pyccapt 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 (300) hide show
  1. {pyccapt-0.2.2/pyccapt.egg-info → pyccapt-0.2.3}/PKG-INFO +82 -2
  2. {pyccapt-0.2.2 → pyccapt-0.2.3}/README.md +77 -1
  3. pyccapt-0.2.3/pyccapt/__init__.py +16 -0
  4. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/DATA_STRUCTURE.md +19 -1
  5. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/clustering/clustering.py +156 -26
  6. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/clustering/isosurface.py +34 -48
  7. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/__init__.py +1 -2
  8. pyccapt-0.2.3/pyccapt/calibration/core/adaptive_residual_calibration.py +981 -0
  9. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/calibration.py +663 -275
  10. pyccapt-0.2.3/pyccapt/calibration/core/correction_models.py +315 -0
  11. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/diagnostics.py +23 -17
  12. pyccapt-0.2.3/pyccapt/calibration/core/event_filters.py +304 -0
  13. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/flight_path_t0.py +51 -11
  14. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/gui_ion_select.py +29 -13
  15. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/hist_bin_optimizer.py +10 -4
  16. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/interactive_point_identification.py +1 -3
  17. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/ion_selection.py +71 -44
  18. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/mc_plot.py +220 -78
  19. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/mc_plot_api.py +54 -14
  20. pyccapt-0.2.3/pyccapt/calibration/core/mc_plot_background_helpers.py +392 -0
  21. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/mc_plot_peak_helpers.py +523 -72
  22. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/mc_plot_selector_helpers.py +1 -2
  23. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/ml_calibration.py +77 -27
  24. pyccapt-0.2.3/pyccapt/calibration/core/new_methods.py +935 -0
  25. pyccapt-0.2.3/pyccapt/calibration/core/parallel.py +235 -0
  26. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/peak_spectral_analysis.py +3 -1
  27. pyccapt-0.2.3/pyccapt/calibration/core/reference_optimizer.py +593 -0
  28. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/share_variables.py +68 -17
  29. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/tools.py +198 -93
  30. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/validation.py +2 -7
  31. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/widgets.py +46 -47
  32. pyccapt-0.2.3/pyccapt/calibration/data_tools/_raw_workflow_common.py +1210 -0
  33. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/_raw_workflow_roentdek.py +104 -3
  34. pyccapt-0.2.3/pyccapt/calibration/data_tools/_raw_workflow_surface_concept.py +2378 -0
  35. pyccapt-0.2.3/pyccapt/calibration/data_tools/ato_tools.py +214 -0
  36. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/data_loadcrop.py +296 -138
  37. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/data_tools.py +198 -69
  38. pyccapt-0.2.3/pyccapt/calibration/data_tools/dataset_path_qt.py +83 -0
  39. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/file_dialog.py +20 -1
  40. pyccapt-0.2.3/pyccapt/calibration/data_tools/hdf5_schema.py +114 -0
  41. pyccapt-0.2.3/pyccapt/calibration/data_tools/lazy_io.py +654 -0
  42. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/merge_range.py +3 -4
  43. pyccapt-0.2.3/pyccapt/calibration/data_tools/partial_hit_diagnostics.py +872 -0
  44. pyccapt-0.2.3/pyccapt/calibration/data_tools/partial_recovery.py +727 -0
  45. pyccapt-0.2.3/pyccapt/calibration/data_tools/plot_vline_draw.py +130 -0
  46. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/raw_data_surface_concept.py +297 -164
  47. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/raw_data_workflow.py +28 -0
  48. pyccapt-0.2.3/pyccapt/calibration/data_tools/run_dataset_path_qt.py +12 -0
  49. pyccapt-0.2.3/pyccapt/calibration/data_tools/run_directory_path_qt.py +11 -0
  50. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/selectors_data.py +7 -3
  51. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cameca_raw/__init__.py +4 -0
  52. pyccapt-0.2.3/pyccapt/calibration/leap_tools/cameca_raw/_rhit_calibration.py +704 -0
  53. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cameca_raw/rhit_tools.py +187 -193
  54. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cameca_raw/str_tools.py +279 -9
  55. pyccapt-0.2.3/pyccapt/calibration/leap_tools/ccapt_tools.py +322 -0
  56. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cloud_plotter.py +0 -1
  57. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/leap_tools.py +53 -79
  58. pyccapt-0.2.3/pyccapt/calibration/leap_tools/matlab_fig_range.py +480 -0
  59. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/mc/mc_tools.py +18 -17
  60. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/mc/tof_tools.py +9 -8
  61. pyccapt-0.2.3/pyccapt/calibration/path_utils.py +84 -0
  62. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/crystal_helper.py +9 -7
  63. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/density_map.py +59 -28
  64. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/fft.py +17 -9
  65. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/io_utils.py +14 -3
  66. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/iso_surface.py +172 -115
  67. pyccapt-0.2.3/pyccapt/calibration/reconstructions/plot_bounds.py +60 -0
  68. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/proxigram.py +117 -39
  69. pyccapt-0.2.3/pyccapt/calibration/reconstructions/rdf.py +156 -0
  70. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/reconstruction.py +290 -219
  71. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/rotation_tools.py +14 -16
  72. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/sdm.py +223 -114
  73. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/tapsim_builder.py +12 -4
  74. pyccapt-0.2.3/pyccapt/calibration/reflectron_correction/batch_cli.py +527 -0
  75. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/core.py +89 -4
  76. pyccapt-0.2.3/pyccapt/config.toml +337 -0
  77. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/CONTROL.md +52 -0
  78. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/DATA_STRUCTURE.md +10 -0
  79. pyccapt-0.2.3/pyccapt/control/__main__.py +133 -0
  80. pyccapt-0.2.3/pyccapt/control/apt/__init__.py +1 -0
  81. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/apt/apt_exp_control.py +356 -78
  82. pyccapt-0.2.3/pyccapt/control/apt/apt_exp_control_func.py +237 -0
  83. pyccapt-0.2.3/pyccapt/control/apt/detector_models.py +25 -0
  84. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/apt/detector_runtime.py +7 -4
  85. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/apt/experiment_state.py +32 -16
  86. pyccapt-0.2.3/pyccapt/control/core/__init__.py +1 -0
  87. pyccapt-0.2.3/pyccapt/control/core/baking_loging.py +236 -0
  88. pyccapt-0.2.3/pyccapt/control/core/com_ports.py +22 -0
  89. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/core/control_data_tool.py +24 -24
  90. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/core/device_checks.py +9 -7
  91. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/core/experiment_statistics.py +35 -6
  92. pyccapt-0.2.3/pyccapt/control/core/hdf5_creator.py +453 -0
  93. pyccapt-0.2.3/pyccapt/control/core/live_calibration.py +895 -0
  94. pyccapt-0.2.3/pyccapt/control/core/loggi.py +466 -0
  95. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/core/read_files.py +2 -5
  96. pyccapt-0.2.3/pyccapt/control/core/recover_chunks_to_hdf5.py +509 -0
  97. pyccapt-0.2.3/pyccapt/control/core/runtime.py +255 -0
  98. pyccapt-0.2.3/pyccapt/control/core/share_variables.py +632 -0
  99. pyccapt-0.2.3/pyccapt/control/core/shared_ring_buffer.py +216 -0
  100. pyccapt-0.2.3/pyccapt/control/core/tof2mc_simple.py +32 -0
  101. pyccapt-0.2.3/pyccapt/control/devices/__init__.py +1 -0
  102. pyccapt-0.2.3/pyccapt/control/devices/camera.py +830 -0
  103. pyccapt-0.2.3/pyccapt/control/devices/edwards_tic.py +55 -0
  104. pyccapt-0.2.3/pyccapt/control/devices/email_send.py +310 -0
  105. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/devices/initialize_devices.py +171 -41
  106. pyccapt-0.2.3/pyccapt/control/devices/pfeiffer_gauges.py +252 -0
  107. pyccapt-0.2.3/pyccapt/control/devices/signal_generator.py +145 -0
  108. pyccapt-0.2.3/pyccapt/control/drs/__init__.py +1 -0
  109. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/drs/drs.py +48 -15
  110. pyccapt-0.2.3/pyccapt/control/gui/__init__.py +1 -0
  111. pyccapt-0.2.3/pyccapt/control/gui/gui_baking.py +516 -0
  112. pyccapt-0.2.3/pyccapt/control/gui/gui_cameras.py +1087 -0
  113. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/gui/gui_gates.py +109 -59
  114. pyccapt-0.2.3/pyccapt/control/gui/gui_laser_control.py +2247 -0
  115. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/gui/gui_main.py +1086 -415
  116. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/gui/gui_pumps_vacuum.py +219 -270
  117. pyccapt-0.2.3/pyccapt/control/gui/gui_stage_control.py +800 -0
  118. pyccapt-0.2.3/pyccapt/control/gui/gui_visualization.py +1938 -0
  119. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/gui/main_parameters.py +2 -4
  120. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/gui/process_coordinator.py +21 -5
  121. pyccapt-0.2.3/pyccapt/control/gui/tooltips.py +432 -0
  122. pyccapt-0.2.3/pyccapt/control/nkt_photonics/nktpbus_activate.py +49 -0
  123. pyccapt-0.2.3/pyccapt/control/nkt_photonics/nktpbus_switch.py +314 -0
  124. pyccapt-0.2.3/pyccapt/control/nkt_photonics/origamiClassCLI.py +404 -0
  125. pyccapt-0.2.3/pyccapt/control/smaract_mcs2/__init__.py +1 -0
  126. pyccapt-0.2.3/pyccapt/control/smaract_mcs2/mcs2_stage.py +436 -0
  127. pyccapt-0.2.3/pyccapt/control/tdc_roentdek/__init__.py +1 -0
  128. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/cobold_tool.py +2 -6
  129. pyccapt-0.2.3/pyccapt/control/tdc_roentdek/tdc_roentdek.py +205 -0
  130. pyccapt-0.2.3/pyccapt/control/tdc_surface_concept/__init__.py +1 -0
  131. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/scTDC.py +156 -188
  132. pyccapt-0.2.3/pyccapt/control/tdc_surface_concept/tdc_surface_concept.py +619 -0
  133. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/_APTAPI.py +61 -62
  134. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/_error_codes.py +1 -1
  135. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/core.py +373 -466
  136. pyccapt-0.2.3/pyccapt/control/thorlabs_apt/thorlab_motor.py +41 -0
  137. {pyccapt-0.2.2/pyccapt/calibration → pyccapt-0.2.3/pyccapt/control/usb_switch}/__init__.py +0 -1
  138. pyccapt-0.2.3/pyccapt/control/usb_switch/usb_switch.py +80 -0
  139. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_cameras.ui +3 -3
  140. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_laser_control.ui +41 -122
  141. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_main.ui +0 -14
  142. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_pumps_vacuum.ui +1 -1
  143. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_stage_control.ui +262 -102
  144. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_visualization.ui +165 -22
  145. pyccapt-0.2.3/pyccapt/files/email_credentials.example.toml +48 -0
  146. {pyccapt-0.2.2 → pyccapt-0.2.3/pyccapt.egg-info}/PKG-INFO +82 -2
  147. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt.egg-info/SOURCES.txt +22 -0
  148. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt.egg-info/requires.txt +6 -0
  149. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyproject.toml +0 -9
  150. {pyccapt-0.2.2 → pyccapt-0.2.3}/setup.py +13 -0
  151. pyccapt-0.2.2/pyccapt/__init__.py +0 -3
  152. pyccapt-0.2.2/pyccapt/calibration/core/adaptive_residual_calibration.py +0 -492
  153. pyccapt-0.2.2/pyccapt/calibration/core/correction_models.py +0 -129
  154. pyccapt-0.2.2/pyccapt/calibration/core/mc_plot_background_helpers.py +0 -176
  155. pyccapt-0.2.2/pyccapt/calibration/data_tools/_raw_workflow_common.py +0 -672
  156. pyccapt-0.2.2/pyccapt/calibration/data_tools/_raw_workflow_surface_concept.py +0 -562
  157. pyccapt-0.2.2/pyccapt/calibration/data_tools/ato_tools.py +0 -168
  158. pyccapt-0.2.2/pyccapt/calibration/data_tools/dataset_path_qt.py +0 -52
  159. pyccapt-0.2.2/pyccapt/calibration/data_tools/plot_vline_draw.py +0 -130
  160. pyccapt-0.2.2/pyccapt/calibration/data_tools/run_dataset_path_qt.py +0 -12
  161. pyccapt-0.2.2/pyccapt/calibration/leap_tools/ccapt_tools.py +0 -197
  162. pyccapt-0.2.2/pyccapt/calibration/path_utils.py +0 -42
  163. pyccapt-0.2.2/pyccapt/calibration/reconstructions/rdf.py +0 -134
  164. pyccapt-0.2.2/pyccapt/config.toml +0 -88
  165. pyccapt-0.2.2/pyccapt/control/__main__.py +0 -48
  166. pyccapt-0.2.2/pyccapt/control/apt/__init__.py +0 -1
  167. pyccapt-0.2.2/pyccapt/control/apt/apt_exp_control_func.py +0 -218
  168. pyccapt-0.2.2/pyccapt/control/core/__init__.py +0 -1
  169. pyccapt-0.2.2/pyccapt/control/core/baking_loging.py +0 -218
  170. pyccapt-0.2.2/pyccapt/control/core/com_ports.py +0 -17
  171. pyccapt-0.2.2/pyccapt/control/core/hdf5_creator.py +0 -185
  172. pyccapt-0.2.2/pyccapt/control/core/loggi.py +0 -33
  173. pyccapt-0.2.2/pyccapt/control/core/runtime.py +0 -117
  174. pyccapt-0.2.2/pyccapt/control/core/share_variables.py +0 -382
  175. pyccapt-0.2.2/pyccapt/control/core/tof2mc_simple.py +0 -32
  176. pyccapt-0.2.2/pyccapt/control/devices/__init__.py +0 -1
  177. pyccapt-0.2.2/pyccapt/control/devices/camera.py +0 -348
  178. pyccapt-0.2.2/pyccapt/control/devices/edwards_tic.py +0 -43
  179. pyccapt-0.2.2/pyccapt/control/devices/email_send.py +0 -63
  180. pyccapt-0.2.2/pyccapt/control/devices/pfeiffer_gauges.py +0 -204
  181. pyccapt-0.2.2/pyccapt/control/devices/signal_generator.py +0 -105
  182. pyccapt-0.2.2/pyccapt/control/drs/__init__.py +0 -1
  183. pyccapt-0.2.2/pyccapt/control/gui/__init__.py +0 -1
  184. pyccapt-0.2.2/pyccapt/control/gui/gui_baking.py +0 -468
  185. pyccapt-0.2.2/pyccapt/control/gui/gui_cameras.py +0 -762
  186. pyccapt-0.2.2/pyccapt/control/gui/gui_laser_control.py +0 -1014
  187. pyccapt-0.2.2/pyccapt/control/gui/gui_stage_control.py +0 -294
  188. pyccapt-0.2.2/pyccapt/control/gui/gui_visualization.py +0 -1243
  189. pyccapt-0.2.2/pyccapt/control/nkt_photonics/nktpbus_activate.py +0 -40
  190. pyccapt-0.2.2/pyccapt/control/nkt_photonics/origamiClassCLI.py +0 -360
  191. pyccapt-0.2.2/pyccapt/control/tdc_roentdek/__init__.py +0 -1
  192. pyccapt-0.2.2/pyccapt/control/tdc_roentdek/tdc_roentdek.py +0 -179
  193. pyccapt-0.2.2/pyccapt/control/tdc_surface_concept/__init__.py +0 -1
  194. pyccapt-0.2.2/pyccapt/control/tdc_surface_concept/tdc_surface_concept.py +0 -439
  195. pyccapt-0.2.2/pyccapt/control/thorlabs_apt/thorlab_motor.py +0 -44
  196. pyccapt-0.2.2/pyccapt/control/usb_switch/usb_switch.py +0 -52
  197. {pyccapt-0.2.2 → pyccapt-0.2.3}/CHANGELOG.txt +0 -0
  198. {pyccapt-0.2.2 → pyccapt-0.2.3}/LICENSE +0 -0
  199. {pyccapt-0.2.2 → pyccapt-0.2.3}/MANIFEST.in +0 -0
  200. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/CALIBRATION.md +0 -0
  201. {pyccapt-0.2.2/pyccapt/control/thorlabs_apt → pyccapt-0.2.3/pyccapt/calibration}/__init__.py +0 -0
  202. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/clustering/__init__.py +0 -0
  203. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/background.py +0 -0
  204. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/exceptions.py +0 -0
  205. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/logging_library.py +0 -0
  206. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/spectrum_simulation.py +0 -0
  207. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/__init__.py +0 -0
  208. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/__init__.py +0 -0
  209. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cameca_raw/README.md +0 -0
  210. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cameca_raw/rhit_extract.py +0 -0
  211. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/mc/__init__.py +0 -0
  212. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/__init__.py +0 -0
  213. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/specimen_builder.py +0 -0
  214. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/__init__.py +0 -0
  215. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/3000XHR_Leoben_21_14093_intersections.csv +0 -0
  216. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/3000XHR_Leoben_21_14093_reference.csv +0 -0
  217. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/3000XHR_Leoben_21_14093_triangles.csv +0 -0
  218. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/4000XHR_Erlangen_56_4833_intersections.csv +0 -0
  219. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/4000XHR_Erlangen_56_4833_reference.csv +0 -0
  220. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/4000XHR_Erlangen_56_4833_triangles.csv +0 -0
  221. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Leoben_5124_368_intersections.csv +0 -0
  222. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Leoben_5124_368_reference.csv +0 -0
  223. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Leoben_5124_368_triangles.csv +0 -0
  224. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Oxford_5083_23091_intersections.csv +0 -0
  225. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Oxford_5083_23091_reference.csv +0 -0
  226. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Oxford_5083_23091_triangles.csv +0 -0
  227. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/6000XR_Chalmers_6002_770_intersections.csv +0 -0
  228. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/6000XR_Chalmers_6002_770_reference.csv +0 -0
  229. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/6000XR_Chalmers_6002_770_triangles.csv +0 -0
  230. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/__init__.py +0 -0
  231. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/drs/README.md +0 -0
  232. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/drs/drs_lib.dll +0 -0
  233. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/drs/drs_lib.exp +0 -0
  234. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/drs/drs_lib.lib +0 -0
  235. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/electrode.toml +0 -0
  236. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/nkt_photonics/__init__.py +0 -0
  237. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/CoboldPC2011R5-2_Header-Template_1TDC8HP.lmf +0 -0
  238. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/README.md +0 -0
  239. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/global.cfg +0 -0
  240. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/source/CoboldPC2011R5-2_Header-Template_1TDC8HP.lmf +0 -0
  241. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/source/global.cfg +0 -0
  242. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/source/hptdc_driver_3.5.2_x64.lib +0 -0
  243. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/source/hptdc_driver_3.5.2_x86.lib +0 -0
  244. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/source/lmf2txt.exe +0 -0
  245. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/wrapper_read_TDC8HP_x64.dll +0 -0
  246. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/README.md +0 -0
  247. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/okFrontPanel.dll +0 -0
  248. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/pthreadVC2.dll +0 -0
  249. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/sc2ddld304_1b_tagrst.bit +0 -0
  250. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/scDeviceClass60.dll +0 -0
  251. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/scTDC1.dll +0 -0
  252. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/scTDC1.lib +0 -0
  253. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/scTDC_hdf50.dll +0 -0
  254. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/tdc_gpx3.ini +0 -0
  255. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APT Server.chm +0 -0
  256. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APT.dll +0 -0
  257. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APT.lib +0 -0
  258. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APTChopper.ocx +0 -0
  259. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APTLaser.ocx +0 -0
  260. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APTPZMotor.ocx +0 -0
  261. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APTQuad.ocx +0 -0
  262. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APTTEC.ocx +0 -0
  263. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/GdiPlus.dll +0 -0
  264. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Comms.dll +0 -0
  265. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Core.dll +0 -0
  266. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Logger.ocx +0 -0
  267. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Motor.ocx +0 -0
  268. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17NanoTrak.ocx +0 -0
  269. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Piezo.ocx +0 -0
  270. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17System.ocx +0 -0
  271. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17UIThread.dll +0 -0
  272. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Utils.dll +0 -0
  273. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/README.md +0 -0
  274. {pyccapt-0.2.2/pyccapt/control/usb_switch → pyccapt-0.2.3/pyccapt/control/thorlabs_apt}/__init__.py +0 -0
  275. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/ftd2xx.dll +0 -0
  276. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/usb_switch/USBaccessX64.dll +0 -0
  277. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/README.md +0 -0
  278. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_baking.ui +0 -0
  279. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_gates.ui +0 -0
  280. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_laser_control_special.ui +0 -0
  281. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/arrow.png +0 -0
  282. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/close_all.png +0 -0
  283. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/color_scheme.h5 +0 -0
  284. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/cryo_load_main_open.png +0 -0
  285. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/cryo_load_open.png +0 -0
  286. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/cryo_open.png +0 -0
  287. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/field_density_table.h5 +0 -0
  288. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/green-led-on.png +0 -0
  289. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/isotopeTable.h5 +0 -0
  290. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/led-orange.png +0 -0
  291. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/led-red-on.png +0 -0
  292. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/load_main_open.png +0 -0
  293. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/load_open.png +0 -0
  294. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/logo.png +0 -0
  295. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/logo2.png +0 -0
  296. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/main_open.png +0 -0
  297. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt.egg-info/dependency_links.txt +0 -0
  298. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt.egg-info/entry_points.txt +0 -0
  299. {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt.egg-info/top_level.txt +0 -0
  300. {pyccapt-0.2.2 → pyccapt-0.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyccapt
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A Python package for atom probe control and data calibration.
5
5
  Home-page: https://github.com/mmonajem/pyccapt
6
6
  Author: Mehrpad Monajem
@@ -26,6 +26,7 @@ Requires-Dist: pandas
26
26
  Requires-Dist: requests
27
27
  Requires-Dist: scipy
28
28
  Requires-Dist: tables
29
+ Requires-Dist: tomli; python_version < "3.11"
29
30
  Requires-Dist: wget
30
31
  Provides-Extra: calibration
31
32
  Requires-Dist: adjustText; extra == "calibration"
@@ -44,6 +45,7 @@ Requires-Dist: pymatgen; extra == "calibration"
44
45
  Requires-Dist: pyvista; extra == "calibration"
45
46
  Requires-Dist: scikit-learn; extra == "calibration"
46
47
  Requires-Dist: tqdm; extra == "calibration"
48
+ Requires-Dist: uproot; extra == "calibration"
47
49
  Requires-Dist: vispy; extra == "calibration"
48
50
  Provides-Extra: control
49
51
  Requires-Dist: mcculw; platform_system == "Windows" and extra == "control"
@@ -73,6 +75,7 @@ Requires-Dist: pymatgen; extra == "full"
73
75
  Requires-Dist: pyvista; extra == "full"
74
76
  Requires-Dist: scikit-learn; extra == "full"
75
77
  Requires-Dist: tqdm; extra == "full"
78
+ Requires-Dist: uproot; extra == "full"
76
79
  Requires-Dist: vispy; extra == "full"
77
80
  Requires-Dist: mcculw; platform_system == "Windows" and extra == "full"
78
81
  Requires-Dist: networkx; extra == "full"
@@ -101,6 +104,7 @@ Requires-Dist: pymatgen; extra == "all"
101
104
  Requires-Dist: pyvista; extra == "all"
102
105
  Requires-Dist: scikit-learn; extra == "all"
103
106
  Requires-Dist: tqdm; extra == "all"
107
+ Requires-Dist: uproot; extra == "all"
104
108
  Requires-Dist: vispy; extra == "all"
105
109
  Requires-Dist: mcculw; platform_system == "Windows" and extra == "all"
106
110
  Requires-Dist: networkx; extra == "all"
@@ -212,6 +216,32 @@ pip install -e ".[control]"
212
216
  pip install -e ".[calibration]"
213
217
  ```
214
218
 
219
+ ### Faster local installs
220
+
221
+ The editable (`-e`) step is fast; most of the time goes into resolving and
222
+ downloading dependencies. A few scientific dependencies are heavy: `numba`
223
+ pulls in `llvmlite` (and backtracks or builds from source if no wheel matches
224
+ your Python), and `tables`/`h5py` build against the HDF5 C library when no
225
+ wheel is available. The dependencies are also unpinned, so pip downloads
226
+ metadata for many candidate versions to satisfy the `numpy` ranges that
227
+ `numba`, `tables`, and `h5py` require.
228
+
229
+ To speed it up:
230
+
231
+ ```bash
232
+ # Resolve/download in parallel with uv
233
+ uv pip install -e ".[full]"
234
+
235
+ # Or let conda provide the heavy binaries, then skip re-resolving them
236
+ conda install -c conda-forge numpy scipy pandas h5py pytables numba matplotlib
237
+ pip install -e . --no-deps
238
+
239
+ # During iterative development, when deps are already installed
240
+ pip install -e . --no-deps
241
+ ```
242
+
243
+ See [docs/installation](https://pyccapt.readthedocs.io/en/latest/installation.html) for details.
244
+
215
245
  ## Running PyCCAPT
216
246
 
217
247
  Start the control application:
@@ -272,6 +302,16 @@ Vacuum logs are written under `pyccapt/files/logs/vacuum`, and baking logs are w
272
302
 
273
303
  PyCCAPT calibration workflows cover detector hit maps, FDM views, mass-spectrum calibration, bowl and voltage correction, reconstruction, and downstream visualization.
274
304
 
305
+ Mass calibration runs as a pipeline: initial `t0`/flight-path estimate →
306
+ voltage correction → bowl correction → optional per-ion-index time-drift
307
+ correction → adaptive per-peak residual fit. A **Config preset** dropdown
308
+ in the data-processing notebook selects between adaptive residual (default),
309
+ adaptive residual with time-drift correction (for long runs with measurable
310
+ drift), and the legacy adaptive residual. A separate NIST reference fit
311
+ rescales the calibrated `m/c` onto reference masses without re-fitting the
312
+ voltage, bowl, or drift terms. See
313
+ [docs/CALIBRATION.md](docs/CALIBRATION.md) for the stages and presets.
314
+
275
315
  ![Mass spectrum](pyccapt/files/readme_images/hist.png)
276
316
 
277
317
  <p align="center">
@@ -305,9 +345,17 @@ matching dld event are preserved untouched. See
305
345
  [docs/Calibration_DATA_STRUCTURE.md](docs/Calibration_DATA_STRUCTURE.md) for
306
346
  the on-disk schema.
307
347
 
348
+ The Surface Concept raw-data workflow can recover physically valid hits from
349
+ pulses that fired only some delay-line channels: each delay-line axis is paired
350
+ and cross-matched by time-of-flight, and recovered rows are tagged with a `dlts`
351
+ count and a `dlts_quality` label. See
352
+ [docs/CALIBRATION.md](docs/CALIBRATION.md#partial-hit-recovery-surface-concept).
353
+
308
354
  The visualization helpers also include optional precipitate clustering with both
309
355
  Min-Max and Maximum-Separation algorithms, plus iso-surface and proxigram
310
- workflows for interface analysis.
356
+ workflows for interface analysis. Scatter sub-sampling in the reconstruction and
357
+ visualization plots is seeded, so re-running a plot on the same dataset produces
358
+ the same figure.
311
359
 
312
360
  For control part of the package you can follow the steps
313
361
  on [documentation](https://pyccapt.readthedocs.io/).
@@ -373,6 +421,38 @@ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for developmen
373
421
  - Issues and bug reports: [GitHub Issues](https://github.com/mmonajem/pyccapt/issues)
374
422
  - Contact: Mehrpad Monajem (`mehrpad.monajem@fau.de`)
375
423
 
424
+ ## Third-party hardware SDKs and libraries
425
+
426
+ PyCCAPT integrates with several pieces of third-party hardware and uses
427
+ the corresponding vendor SDKs / Python wrappers. These remain the
428
+ intellectual property of their respective owners; the files under the
429
+ listed paths are either thin wrappers around vendor APIs or are
430
+ adapted from vendor-provided example code, and are used here under the
431
+ licence terms shipped with each SDK. Where a wrapper is largely
432
+ vendor-provided code, the source file carries an attribution header.
433
+
434
+ | Component | Vendor / project | Used by |
435
+ |----------------------------------------------------|------------------------------------------|----------------------------------------------------------------------------------------------------------------|
436
+ | Origami XPS laser CLI | NKT Photonics | `pyccapt/control/nkt_photonics/origamiClassCLI.py`, `nktpbus_activate.py` |
437
+ | NKTPDLL (NKTPBus protocol DLL + Python wrapper) | NKT Photonics | `pyccapt/control/nkt_photonics/nktpbus_switch.py` (loads the vendor's `NKTPDLL.dll` and bundled `NKTP_DLL.py`) |
438
+ | MCS2 stage controller SDK (`smaract.ctl`) | SmarAct GmbH | `pyccapt/control/smaract_mcs2/` |
439
+ | Surface Concept TDC SDK (`scTDC`) | Surface Concept GmbH | `pyccapt/control/tdc_surface_concept/` |
440
+ | RoentDek TDC8HP wrapper | RoentDek Handels GmbH | `pyccapt/control/tdc_roentdek/` |
441
+ | DRS digitizer library | Paul Scherrer Institute (PSI) | `pyccapt/control/drs/` |
442
+ | Thorlabs APT motor SDK | Thorlabs Inc. | `pyccapt/control/thorlabs_apt/` |
443
+ | Pfeiffer TPG362 vacuum-gauge protocol | Pfeiffer Vacuum | `pyccapt/control/devices/pfeiffer_gauges.py` |
444
+ | Edwards TIC AGC vacuum-controller protocol | Edwards Vacuum | `pyccapt/control/devices/edwards_tic.py` |
445
+ | CryoVac TIC 500 temperature-controller protocol | CryoVac GmbH | `pyccapt/control/devices/initialize_devices.py` (`command_cryovac` and friends) |
446
+ | MCC Universal Library (`mcculw`) for thermocouples | Measurement Computing | `pyccapt/control/core/baking_loging.py` |
447
+ | `simple_pid` PID controller | Martin Lundberg (MIT licence) | `pyccapt/control/apt/apt_exp_control.py` |
448
+ | PyQt6 | Riverbank Computing (GPLv3 / commercial) | All GUI windows under `pyccapt/control/gui/` |
449
+
450
+ To use a given component, install the vendor's SDK or Python package per
451
+ their documentation (see `requirements.txt` / `pyproject.toml` for pip
452
+ packages, and the vendor's installer for the proprietary SDKs that ship
453
+ DLLs). A missing SDK only disables the corresponding device — the rest
454
+ of PyCCAPT continues to work.
455
+
376
456
  ## License
377
457
 
378
458
  PyCCAPT is licensed under the GNU General Public License v3.0. See [LICENSE](LICENSE).
@@ -79,6 +79,32 @@ pip install -e ".[control]"
79
79
  pip install -e ".[calibration]"
80
80
  ```
81
81
 
82
+ ### Faster local installs
83
+
84
+ The editable (`-e`) step is fast; most of the time goes into resolving and
85
+ downloading dependencies. A few scientific dependencies are heavy: `numba`
86
+ pulls in `llvmlite` (and backtracks or builds from source if no wheel matches
87
+ your Python), and `tables`/`h5py` build against the HDF5 C library when no
88
+ wheel is available. The dependencies are also unpinned, so pip downloads
89
+ metadata for many candidate versions to satisfy the `numpy` ranges that
90
+ `numba`, `tables`, and `h5py` require.
91
+
92
+ To speed it up:
93
+
94
+ ```bash
95
+ # Resolve/download in parallel with uv
96
+ uv pip install -e ".[full]"
97
+
98
+ # Or let conda provide the heavy binaries, then skip re-resolving them
99
+ conda install -c conda-forge numpy scipy pandas h5py pytables numba matplotlib
100
+ pip install -e . --no-deps
101
+
102
+ # During iterative development, when deps are already installed
103
+ pip install -e . --no-deps
104
+ ```
105
+
106
+ See [docs/installation](https://pyccapt.readthedocs.io/en/latest/installation.html) for details.
107
+
82
108
  ## Running PyCCAPT
83
109
 
84
110
  Start the control application:
@@ -139,6 +165,16 @@ Vacuum logs are written under `pyccapt/files/logs/vacuum`, and baking logs are w
139
165
 
140
166
  PyCCAPT calibration workflows cover detector hit maps, FDM views, mass-spectrum calibration, bowl and voltage correction, reconstruction, and downstream visualization.
141
167
 
168
+ Mass calibration runs as a pipeline: initial `t0`/flight-path estimate →
169
+ voltage correction → bowl correction → optional per-ion-index time-drift
170
+ correction → adaptive per-peak residual fit. A **Config preset** dropdown
171
+ in the data-processing notebook selects between adaptive residual (default),
172
+ adaptive residual with time-drift correction (for long runs with measurable
173
+ drift), and the legacy adaptive residual. A separate NIST reference fit
174
+ rescales the calibrated `m/c` onto reference masses without re-fitting the
175
+ voltage, bowl, or drift terms. See
176
+ [docs/CALIBRATION.md](docs/CALIBRATION.md) for the stages and presets.
177
+
142
178
  ![Mass spectrum](pyccapt/files/readme_images/hist.png)
143
179
 
144
180
  <p align="center">
@@ -172,9 +208,17 @@ matching dld event are preserved untouched. See
172
208
  [docs/Calibration_DATA_STRUCTURE.md](docs/Calibration_DATA_STRUCTURE.md) for
173
209
  the on-disk schema.
174
210
 
211
+ The Surface Concept raw-data workflow can recover physically valid hits from
212
+ pulses that fired only some delay-line channels: each delay-line axis is paired
213
+ and cross-matched by time-of-flight, and recovered rows are tagged with a `dlts`
214
+ count and a `dlts_quality` label. See
215
+ [docs/CALIBRATION.md](docs/CALIBRATION.md#partial-hit-recovery-surface-concept).
216
+
175
217
  The visualization helpers also include optional precipitate clustering with both
176
218
  Min-Max and Maximum-Separation algorithms, plus iso-surface and proxigram
177
- workflows for interface analysis.
219
+ workflows for interface analysis. Scatter sub-sampling in the reconstruction and
220
+ visualization plots is seeded, so re-running a plot on the same dataset produces
221
+ the same figure.
178
222
 
179
223
  For control part of the package you can follow the steps
180
224
  on [documentation](https://pyccapt.readthedocs.io/).
@@ -240,6 +284,38 @@ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for developmen
240
284
  - Issues and bug reports: [GitHub Issues](https://github.com/mmonajem/pyccapt/issues)
241
285
  - Contact: Mehrpad Monajem (`mehrpad.monajem@fau.de`)
242
286
 
287
+ ## Third-party hardware SDKs and libraries
288
+
289
+ PyCCAPT integrates with several pieces of third-party hardware and uses
290
+ the corresponding vendor SDKs / Python wrappers. These remain the
291
+ intellectual property of their respective owners; the files under the
292
+ listed paths are either thin wrappers around vendor APIs or are
293
+ adapted from vendor-provided example code, and are used here under the
294
+ licence terms shipped with each SDK. Where a wrapper is largely
295
+ vendor-provided code, the source file carries an attribution header.
296
+
297
+ | Component | Vendor / project | Used by |
298
+ |----------------------------------------------------|------------------------------------------|----------------------------------------------------------------------------------------------------------------|
299
+ | Origami XPS laser CLI | NKT Photonics | `pyccapt/control/nkt_photonics/origamiClassCLI.py`, `nktpbus_activate.py` |
300
+ | NKTPDLL (NKTPBus protocol DLL + Python wrapper) | NKT Photonics | `pyccapt/control/nkt_photonics/nktpbus_switch.py` (loads the vendor's `NKTPDLL.dll` and bundled `NKTP_DLL.py`) |
301
+ | MCS2 stage controller SDK (`smaract.ctl`) | SmarAct GmbH | `pyccapt/control/smaract_mcs2/` |
302
+ | Surface Concept TDC SDK (`scTDC`) | Surface Concept GmbH | `pyccapt/control/tdc_surface_concept/` |
303
+ | RoentDek TDC8HP wrapper | RoentDek Handels GmbH | `pyccapt/control/tdc_roentdek/` |
304
+ | DRS digitizer library | Paul Scherrer Institute (PSI) | `pyccapt/control/drs/` |
305
+ | Thorlabs APT motor SDK | Thorlabs Inc. | `pyccapt/control/thorlabs_apt/` |
306
+ | Pfeiffer TPG362 vacuum-gauge protocol | Pfeiffer Vacuum | `pyccapt/control/devices/pfeiffer_gauges.py` |
307
+ | Edwards TIC AGC vacuum-controller protocol | Edwards Vacuum | `pyccapt/control/devices/edwards_tic.py` |
308
+ | CryoVac TIC 500 temperature-controller protocol | CryoVac GmbH | `pyccapt/control/devices/initialize_devices.py` (`command_cryovac` and friends) |
309
+ | MCC Universal Library (`mcculw`) for thermocouples | Measurement Computing | `pyccapt/control/core/baking_loging.py` |
310
+ | `simple_pid` PID controller | Martin Lundberg (MIT licence) | `pyccapt/control/apt/apt_exp_control.py` |
311
+ | PyQt6 | Riverbank Computing (GPLv3 / commercial) | All GUI windows under `pyccapt/control/gui/` |
312
+
313
+ To use a given component, install the vendor's SDK or Python package per
314
+ their documentation (see `requirements.txt` / `pyproject.toml` for pip
315
+ packages, and the vendor's installer for the proprietary SDKs that ship
316
+ DLLs). A missing SDK only disables the corresponding device — the rest
317
+ of PyCCAPT continues to work.
318
+
243
319
  ## License
244
320
 
245
321
  PyCCAPT is licensed under the GNU General Public License v3.0. See [LICENSE](LICENSE).
@@ -0,0 +1,16 @@
1
+ """Top-level package for PyCCAPT."""
2
+
3
+ import warnings
4
+
5
+ # SciPy ships a runtime guard that complains when it's loaded against a
6
+ # newer NumPy than the one it was built for. Our lab environment runs
7
+ # NumPy 1.26 against an older SciPy build, and the bound is advisory
8
+ # (the SciPy routines we use work fine). Filter the specific message
9
+ # here, before any submodule pulls in scipy, so startup logs stay clean.
10
+ warnings.filterwarnings(
11
+ "ignore",
12
+ message=r"A NumPy version >=.* and <.* is required for this version of SciPy.*",
13
+ category=UserWarning,
14
+ )
15
+
16
+ __version__ = "0.2.3"
@@ -3,6 +3,12 @@
3
3
  This document summarizes the data layout used by the calibration module and its
4
4
  range files.
5
5
 
6
+ The canonical machine-readable schema for the raw acquisition groups
7
+ (`/dld`, `/tdc`, `/hsd`) — column names, order, dtypes, and the group
8
+ aliases accepted across pyccapt versions — is defined in
9
+ `pyccapt.calibration.data_tools.hdf5_schema`. The control-side writer
10
+ and the calibration-side reader both follow that module.
11
+
6
12
  ## Notation
7
13
 
8
14
  - `(n,)`: one-dimensional array with length `n`
@@ -26,7 +32,19 @@ Typical calibrated dataset fields:
26
32
  - `y_det (cm)`: `(n,)` `(cm, float64)` detector y hit position
27
33
  - `delta_p`: `(n,)` `(N/A, uint32)` pulses since previous detected event
28
34
  - `multi`: `(n,)` `(N/A, uint32)` multiplicity per pulse
29
- - `start_counter`: `(n,)` `(N/A, float64)` TDC counter value
35
+ - `start_counter`: `(n,)` `(N/A, float64)` TDC counter value (wraps; not unique)
36
+ - `event_group_id` *(optional)*: `(n,)` `(N/A, int64)` shared id linking each
37
+ dld row to the matching raw `/tdc` rows; present when the dataset was loaded
38
+ with `load_tdc_raw=True` and preserved through every cropping step.
39
+
40
+ When partial-hit recovery (`data_tools.partial_recovery`) runs, the DLD
41
+ dataframe gains two columns: `dlts` `(N/A, int8)` — `4` for a native or
42
+ fully-recovered two-axis hit, `2` for a single-axis partial — and
43
+ `dlts_quality` `(N/A, string)` provenance label (`native` for original rows;
44
+ `recovered_xy` / `recovered_x` / `recovered_y` for recovered rows; and
45
+ `recovered_xy_3of4` for a full (x, y) hit rebuilt from a 3-channel pulse via
46
+ the delay-line time-sum constraint). Single-axis recovered rows hold `NaN` on
47
+ the unrecovered detector axis.
30
48
 
31
49
  ## Range Dataset (HDF5 or Imported `.rrng` / `.rng`)
32
50
 
@@ -96,6 +96,17 @@ def normalize_clustering_method(method: str) -> str:
96
96
 
97
97
 
98
98
  def _resolve_xyz(variables) -> np.ndarray:
99
+ """Return the (N, 3) reconstruction coordinates.
100
+
101
+ Partial-recovered rows have NaN x/y/z because their detector position
102
+ was incomplete. ``np.column_stack`` happily carries the NaN through,
103
+ but downstream clustering (DBSCAN / HDBSCAN / MinMax) raises on NaN
104
+ inputs. The mask is preserved at full length here so downstream
105
+ consumers like the colour / mc array stay aligned; callers that
106
+ actually pass the array into clustering must drop NaN rows with
107
+ ``np.isnan(coords).any(axis=1)``. We surface a clear notice the first
108
+ time so the omission is obvious.
109
+ """
99
110
  x = np.asarray(getattr(variables, "x", np.zeros(0)))
100
111
  y = np.asarray(getattr(variables, "y", np.zeros(0)))
101
112
  z = np.asarray(getattr(variables, "z", np.zeros(0)))
@@ -103,7 +114,16 @@ def _resolve_xyz(variables) -> np.ndarray:
103
114
  raise ValueError("Reconstruction coordinates are empty. Run the reconstruction first.")
104
115
  if not (len(x) == len(y) == len(z)):
105
116
  raise ValueError("Reconstruction coordinates must have the same length.")
106
- return np.column_stack((x, y, z))
117
+ coords = np.column_stack((x, y, z))
118
+ n_nan = int(np.isnan(coords).any(axis=1).sum())
119
+ if n_nan > 0:
120
+ print(
121
+ f'[clustering._resolve_xyz] Note: {n_nan} rows have NaN (x, y, z) '
122
+ '(partial-recovered ions with undefined detector position). '
123
+ 'Downstream clustering should mask them out with '
124
+ '``~np.isnan(coords).any(axis=1)``.'
125
+ )
126
+ return coords
107
127
 
108
128
 
109
129
  def _resolve_mc(variables) -> np.ndarray:
@@ -216,34 +236,60 @@ def _drop_small_clusters(labels: np.ndarray, *, n_min: int) -> np.ndarray:
216
236
  return np.array([remap[int(label)] if label >= 0 else -1 for label in labels], dtype=int)
217
237
 
218
238
 
219
- def min_max_clustering(points: np.ndarray, n_clusters: int = 2, max_iter: int = 50) -> tuple[np.ndarray, np.ndarray]:
220
- """Segment points with a deterministic Min-Max initialization plus centroid refinement."""
239
+ def min_max_clustering(
240
+ points: np.ndarray,
241
+ n_clusters: int = 2,
242
+ max_iter: int = 50,
243
+ n_min: int | None = None,
244
+ ) -> tuple[np.ndarray, np.ndarray]:
245
+ """Segment points with a deterministic Min-Max initialization plus centroid refinement.
246
+
247
+ Parameters
248
+ ----------
249
+ n_min : optional int
250
+ When provided, clusters with fewer than ``n_min`` members are
251
+ relabelled as noise (-1) and the surviving labels compacted --
252
+ consistent with the HDBSCAN / DBSCAN / maximum-separation
253
+ algorithms in this module, which all drop tiny clusters. The
254
+ default (None) preserves the legacy behaviour of returning
255
+ exactly ``n_clusters`` partitions.
256
+
257
+ Notes
258
+ -----
259
+ NaN-coordinate rows (partial-recovered ions) are dropped before
260
+ clustering and re-inserted afterwards with label -1; previously the
261
+ NaN values flowed through ``np.linalg.norm`` / ``argmin`` and
262
+ silently produced garbage labels.
263
+ """
221
264
  points = np.asarray(points, dtype=float)
222
265
  if points.ndim != 2 or points.shape[1] != 3:
223
266
  raise ValueError("points must be a (N, 3) array")
224
267
  if n_clusters < 2:
225
268
  raise ValueError("n_clusters must be at least 2")
226
- if len(points) < n_clusters:
227
- raise ValueError("Not enough points for the requested number of clusters")
228
269
 
229
- centroid = points.mean(axis=0)
230
- first_index = int(np.argmax(np.linalg.norm(points - centroid, axis=1)))
231
- centers = [points[first_index]]
270
+ nan_row_mask = np.isnan(points).any(axis=1)
271
+ finite_points = points[~nan_row_mask]
272
+ if len(finite_points) < n_clusters:
273
+ raise ValueError("Not enough (finite) points for the requested number of clusters")
274
+
275
+ centroid = finite_points.mean(axis=0)
276
+ first_index = int(np.argmax(np.linalg.norm(finite_points - centroid, axis=1)))
277
+ centers = [finite_points[first_index]]
232
278
 
233
279
  while len(centers) < n_clusters:
234
- distances = np.stack([np.linalg.norm(points - center, axis=1) for center in centers], axis=1)
280
+ distances = np.stack([np.linalg.norm(finite_points - center, axis=1) for center in centers], axis=1)
235
281
  candidate_index = int(np.argmax(np.min(distances, axis=1)))
236
- centers.append(points[candidate_index])
282
+ centers.append(finite_points[candidate_index])
237
283
 
238
284
  centers = np.asarray(centers, dtype=float)
239
- labels = np.zeros(len(points), dtype=int)
285
+ labels = np.zeros(len(finite_points), dtype=int)
240
286
 
241
287
  for _ in range(max_iter):
242
- distances = np.stack([np.linalg.norm(points - center, axis=1) for center in centers], axis=1)
288
+ distances = np.stack([np.linalg.norm(finite_points - center, axis=1) for center in centers], axis=1)
243
289
  new_labels = np.argmin(distances, axis=1)
244
290
  new_centers = centers.copy()
245
291
  for idx in range(n_clusters):
246
- cluster_points = points[new_labels == idx]
292
+ cluster_points = finite_points[new_labels == idx]
247
293
  if len(cluster_points) > 0:
248
294
  new_centers[idx] = cluster_points.mean(axis=0)
249
295
  if np.array_equal(new_labels, labels) and np.allclose(new_centers, centers):
@@ -257,6 +303,18 @@ def min_max_clustering(points: np.ndarray, n_clusters: int = 2, max_iter: int =
257
303
  remap = {int(old): int(new) for new, old in enumerate(order)}
258
304
  labels = np.array([remap[int(label)] for label in labels], dtype=int)
259
305
  centers = centers[order]
306
+
307
+ if n_min is not None:
308
+ labels = _drop_small_clusters(labels, n_min=int(n_min))
309
+ centers = _centers_from_labeled_points(finite_points, labels)
310
+
311
+ if nan_row_mask.any():
312
+ # Re-expand to the original input length so callers that index by
313
+ # selection mask stay aligned; NaN rows are noise (-1).
314
+ full = np.full(len(points), -1, dtype=int)
315
+ full[~nan_row_mask] = labels
316
+ labels = full
317
+
260
318
  return labels, centers
261
319
 
262
320
 
@@ -289,6 +347,19 @@ def estimate_maximum_separation_distance(
289
347
  return d_max
290
348
 
291
349
 
350
+ def _expand_maxsep_labels(filtered_labels: np.ndarray, finite_idx: np.ndarray, input_len: int) -> np.ndarray:
351
+ """Re-expand labels computed on the NaN-filtered subset to full length.
352
+
353
+ Dropped (NaN-coordinate) rows become -1 (noise) so callers that index
354
+ by the original selection mask stay aligned.
355
+ """
356
+ if len(filtered_labels) == input_len:
357
+ return filtered_labels
358
+ full = np.full(input_len, -1, dtype=int)
359
+ full[finite_idx] = filtered_labels
360
+ return full
361
+
362
+
292
363
  def maximum_separation_clustering(
293
364
  points: np.ndarray,
294
365
  *,
@@ -308,10 +379,24 @@ def maximum_separation_clustering(
308
379
  if n_min < 2:
309
380
  raise ValueError("n_min must be at least 2")
310
381
 
382
+ # NaN-coordinate rows (partial-recovered ions) would break cKDTree;
383
+ # drop them up front and re-expand labels to the original length with
384
+ # -1 (noise) afterwards, consistent with hdbscan_clustering and
385
+ # min_max_clustering. ``input_len`` / ``finite_idx`` carry the mapping.
386
+ input_len = len(points)
387
+ nan_row_mask = np.isnan(points).any(axis=1)
388
+ finite_idx = np.flatnonzero(~nan_row_mask)
389
+ if nan_row_mask.any():
390
+ print(
391
+ f'[maximum_separation_clustering] Dropping {int(nan_row_mask.sum())} '
392
+ 'rows with NaN (x, y, z) (partial-recovered ions) before clustering.'
393
+ )
394
+ points = points[finite_idx]
395
+
311
396
  n_points = len(points)
312
397
  labels = np.full(n_points, -1, dtype=int)
313
398
  if n_points < n_min:
314
- return labels, np.empty((0, 3), dtype=float)
399
+ return _expand_maxsep_labels(labels, finite_idx, input_len), np.empty((0, 3), dtype=float)
315
400
 
316
401
  tree = cKDTree(points)
317
402
  try:
@@ -320,7 +405,7 @@ def maximum_separation_clustering(
320
405
  pairs = np.asarray(sorted(tree.query_pairs(d_max)), dtype=int)
321
406
 
322
407
  if pairs.size == 0:
323
- return labels, np.empty((0, 3), dtype=float)
408
+ return _expand_maxsep_labels(labels, finite_idx, input_len), np.empty((0, 3), dtype=float)
324
409
 
325
410
  parent = np.arange(n_points, dtype=int)
326
411
  size = np.ones(n_points, dtype=int)
@@ -346,9 +431,13 @@ def maximum_separation_clustering(
346
431
 
347
432
  roots = np.fromiter((find(index) for index in range(n_points)), count=n_points, dtype=int)
348
433
  unique_roots, inverse, counts = np.unique(roots, return_inverse=True, return_counts=True)
434
+ # numpy 2.0 briefly returned ``inverse`` with the input's shape
435
+ # (i.e. (n, 1) here); ravel so the ``enumerate(inverse)`` loop below
436
+ # always yields scalar local_root_id values.
437
+ inverse = np.ravel(inverse)
349
438
  valid_mask = counts >= n_min
350
439
  if not np.any(valid_mask):
351
- return labels, np.empty((0, 3), dtype=float)
440
+ return _expand_maxsep_labels(labels, finite_idx, input_len), np.empty((0, 3), dtype=float)
352
441
 
353
442
  centers = []
354
443
  cluster_sizes = []
@@ -366,12 +455,19 @@ def maximum_separation_clustering(
366
455
 
367
456
  remap = {int(old_idx): int(new_idx) for new_idx, old_idx in enumerate(order)}
368
457
  for point_index, local_root_id in enumerate(inverse):
458
+ local_root_id = int(local_root_id)
369
459
  if not valid_mask[local_root_id]:
370
460
  continue
371
- labels[point_index] = remap[old_to_new[int(local_root_id)]]
461
+ # Defensive .get: old_to_new only holds valid roots, and the
462
+ # valid_mask guard above already gates entry, but a guard here
463
+ # makes the relabel robust to any future index-space drift.
464
+ center_idx = old_to_new.get(local_root_id)
465
+ if center_idx is None:
466
+ continue
467
+ labels[point_index] = remap[center_idx]
372
468
 
373
469
  centers = centers[order]
374
- return labels, centers
470
+ return _expand_maxsep_labels(labels, finite_idx, input_len), centers
375
471
 
376
472
 
377
473
  def hdbscan_clustering(
@@ -390,6 +486,26 @@ def hdbscan_clustering(
390
486
  if len(points) == 0:
391
487
  raise ValueError("points cannot be empty")
392
488
 
489
+ # Both HDBSCAN and sklearn DBSCAN raise on NaN coordinates. Drop
490
+ # partial-recovered rows with undefined (x, y, z) before clustering
491
+ # rather than letting the backend explode. ``labels`` is returned at
492
+ # FULL input length: NaN rows get label -1 (noise) so the caller's
493
+ # ``full_labels[selected_indices] = labels`` assignment continues to
494
+ # line up with ``selected_indices``. Previously the returned labels
495
+ # had the filtered length and broadcasting to a longer index slice
496
+ # crashed with ValueError (or silently misaligned in pre-numpy 1.20
497
+ # behaviour, putting every label after the first NaN row on the
498
+ # wrong ion).
499
+ nan_row_mask = np.isnan(points).any(axis=1)
500
+ finite_points = points[~nan_row_mask]
501
+ if nan_row_mask.any():
502
+ print(
503
+ f'[hdbscan_clustering] Dropping {int(nan_row_mask.sum())} rows with NaN '
504
+ '(x, y, z) (partial-recovered ions) before clustering.'
505
+ )
506
+ if len(finite_points) == 0:
507
+ raise ValueError("points has no finite rows after dropping NaN coordinates")
508
+
393
509
  n_min = max(2, int(n_min))
394
510
  min_samples = max(1, int(min_samples))
395
511
  parameters: dict[str, float | int | bool] = {
@@ -397,13 +513,22 @@ def hdbscan_clustering(
397
513
  "min_samples": int(min_samples),
398
514
  }
399
515
 
516
+ def _expand_labels(filtered_labels: np.ndarray) -> np.ndarray:
517
+ """Expand labels back to full input length; NaN rows get -1 (noise)."""
518
+ full = np.full(len(points), -1, dtype=int)
519
+ full[~nan_row_mask] = filtered_labels
520
+ return full
521
+
400
522
  try:
401
523
  import hdbscan as _hdbscan
402
524
 
403
525
  clusterer = _hdbscan.HDBSCAN(min_cluster_size=n_min, min_samples=min_samples)
404
- labels = np.asarray(clusterer.fit_predict(points), dtype=int)
405
- labels = _drop_small_clusters(labels, n_min=n_min)
406
- centers = _centers_from_labeled_points(points, labels)
526
+ filtered_labels = np.asarray(clusterer.fit_predict(finite_points), dtype=int)
527
+ filtered_labels = _drop_small_clusters(filtered_labels, n_min=n_min)
528
+ # Compute centers from the filtered (NaN-free) points so the
529
+ # centroid calculation isn't poisoned by undefined coords.
530
+ centers = _centers_from_labeled_points(finite_points, filtered_labels)
531
+ labels = _expand_labels(filtered_labels)
407
532
  parameters["backend"] = True
408
533
  return labels, centers, parameters
409
534
  except Exception:
@@ -412,16 +537,21 @@ def hdbscan_clustering(
412
537
 
413
538
  if auto_d_max or d_max is None:
414
539
  eps = estimate_maximum_separation_distance(
415
- points,
540
+ finite_points,
416
541
  kth_neighbor=max(1, min_samples),
417
542
  percentile=float(percentile),
418
543
  )
419
544
  else:
420
545
  eps = float(d_max)
421
- labels = np.asarray(DBSCAN(eps=eps, min_samples=n_min).fit_predict(points), dtype=int)
422
- labels = _drop_small_clusters(labels, n_min=n_min)
423
- centers = _centers_from_labeled_points(points, labels)
424
- parameters.update({"backend": False, "d_max": float(eps), "auto_d_max": bool(auto_d_max), "percentile": float(percentile)})
546
+ filtered_labels = np.asarray(
547
+ DBSCAN(eps=eps, min_samples=n_min).fit_predict(finite_points), dtype=int
548
+ )
549
+ filtered_labels = _drop_small_clusters(filtered_labels, n_min=n_min)
550
+ centers = _centers_from_labeled_points(finite_points, filtered_labels)
551
+ labels = _expand_labels(filtered_labels)
552
+ parameters.update(
553
+ {"backend": False, "d_max": float(eps), "auto_d_max": bool(auto_d_max), "percentile": float(percentile)}
554
+ )
425
555
  return labels, centers, parameters
426
556
 
427
557