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.
- {pyccapt-0.2.2/pyccapt.egg-info → pyccapt-0.2.3}/PKG-INFO +82 -2
- {pyccapt-0.2.2 → pyccapt-0.2.3}/README.md +77 -1
- pyccapt-0.2.3/pyccapt/__init__.py +16 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/DATA_STRUCTURE.md +19 -1
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/clustering/clustering.py +156 -26
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/clustering/isosurface.py +34 -48
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/__init__.py +1 -2
- pyccapt-0.2.3/pyccapt/calibration/core/adaptive_residual_calibration.py +981 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/calibration.py +663 -275
- pyccapt-0.2.3/pyccapt/calibration/core/correction_models.py +315 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/diagnostics.py +23 -17
- pyccapt-0.2.3/pyccapt/calibration/core/event_filters.py +304 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/flight_path_t0.py +51 -11
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/gui_ion_select.py +29 -13
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/hist_bin_optimizer.py +10 -4
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/interactive_point_identification.py +1 -3
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/ion_selection.py +71 -44
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/mc_plot.py +220 -78
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/mc_plot_api.py +54 -14
- pyccapt-0.2.3/pyccapt/calibration/core/mc_plot_background_helpers.py +392 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/mc_plot_peak_helpers.py +523 -72
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/mc_plot_selector_helpers.py +1 -2
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/ml_calibration.py +77 -27
- pyccapt-0.2.3/pyccapt/calibration/core/new_methods.py +935 -0
- pyccapt-0.2.3/pyccapt/calibration/core/parallel.py +235 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/peak_spectral_analysis.py +3 -1
- pyccapt-0.2.3/pyccapt/calibration/core/reference_optimizer.py +593 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/share_variables.py +68 -17
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/tools.py +198 -93
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/validation.py +2 -7
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/widgets.py +46 -47
- pyccapt-0.2.3/pyccapt/calibration/data_tools/_raw_workflow_common.py +1210 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/_raw_workflow_roentdek.py +104 -3
- pyccapt-0.2.3/pyccapt/calibration/data_tools/_raw_workflow_surface_concept.py +2378 -0
- pyccapt-0.2.3/pyccapt/calibration/data_tools/ato_tools.py +214 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/data_loadcrop.py +296 -138
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/data_tools.py +198 -69
- pyccapt-0.2.3/pyccapt/calibration/data_tools/dataset_path_qt.py +83 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/file_dialog.py +20 -1
- pyccapt-0.2.3/pyccapt/calibration/data_tools/hdf5_schema.py +114 -0
- pyccapt-0.2.3/pyccapt/calibration/data_tools/lazy_io.py +654 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/merge_range.py +3 -4
- pyccapt-0.2.3/pyccapt/calibration/data_tools/partial_hit_diagnostics.py +872 -0
- pyccapt-0.2.3/pyccapt/calibration/data_tools/partial_recovery.py +727 -0
- pyccapt-0.2.3/pyccapt/calibration/data_tools/plot_vline_draw.py +130 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/raw_data_surface_concept.py +297 -164
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/raw_data_workflow.py +28 -0
- pyccapt-0.2.3/pyccapt/calibration/data_tools/run_dataset_path_qt.py +12 -0
- pyccapt-0.2.3/pyccapt/calibration/data_tools/run_directory_path_qt.py +11 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/selectors_data.py +7 -3
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cameca_raw/__init__.py +4 -0
- pyccapt-0.2.3/pyccapt/calibration/leap_tools/cameca_raw/_rhit_calibration.py +704 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cameca_raw/rhit_tools.py +187 -193
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cameca_raw/str_tools.py +279 -9
- pyccapt-0.2.3/pyccapt/calibration/leap_tools/ccapt_tools.py +322 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cloud_plotter.py +0 -1
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/leap_tools.py +53 -79
- pyccapt-0.2.3/pyccapt/calibration/leap_tools/matlab_fig_range.py +480 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/mc/mc_tools.py +18 -17
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/mc/tof_tools.py +9 -8
- pyccapt-0.2.3/pyccapt/calibration/path_utils.py +84 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/crystal_helper.py +9 -7
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/density_map.py +59 -28
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/fft.py +17 -9
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/io_utils.py +14 -3
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/iso_surface.py +172 -115
- pyccapt-0.2.3/pyccapt/calibration/reconstructions/plot_bounds.py +60 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/proxigram.py +117 -39
- pyccapt-0.2.3/pyccapt/calibration/reconstructions/rdf.py +156 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/reconstruction.py +290 -219
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/rotation_tools.py +14 -16
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/sdm.py +223 -114
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/tapsim_builder.py +12 -4
- pyccapt-0.2.3/pyccapt/calibration/reflectron_correction/batch_cli.py +527 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/core.py +89 -4
- pyccapt-0.2.3/pyccapt/config.toml +337 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/CONTROL.md +52 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/DATA_STRUCTURE.md +10 -0
- pyccapt-0.2.3/pyccapt/control/__main__.py +133 -0
- pyccapt-0.2.3/pyccapt/control/apt/__init__.py +1 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/apt/apt_exp_control.py +356 -78
- pyccapt-0.2.3/pyccapt/control/apt/apt_exp_control_func.py +237 -0
- pyccapt-0.2.3/pyccapt/control/apt/detector_models.py +25 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/apt/detector_runtime.py +7 -4
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/apt/experiment_state.py +32 -16
- pyccapt-0.2.3/pyccapt/control/core/__init__.py +1 -0
- pyccapt-0.2.3/pyccapt/control/core/baking_loging.py +236 -0
- pyccapt-0.2.3/pyccapt/control/core/com_ports.py +22 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/core/control_data_tool.py +24 -24
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/core/device_checks.py +9 -7
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/core/experiment_statistics.py +35 -6
- pyccapt-0.2.3/pyccapt/control/core/hdf5_creator.py +453 -0
- pyccapt-0.2.3/pyccapt/control/core/live_calibration.py +895 -0
- pyccapt-0.2.3/pyccapt/control/core/loggi.py +466 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/core/read_files.py +2 -5
- pyccapt-0.2.3/pyccapt/control/core/recover_chunks_to_hdf5.py +509 -0
- pyccapt-0.2.3/pyccapt/control/core/runtime.py +255 -0
- pyccapt-0.2.3/pyccapt/control/core/share_variables.py +632 -0
- pyccapt-0.2.3/pyccapt/control/core/shared_ring_buffer.py +216 -0
- pyccapt-0.2.3/pyccapt/control/core/tof2mc_simple.py +32 -0
- pyccapt-0.2.3/pyccapt/control/devices/__init__.py +1 -0
- pyccapt-0.2.3/pyccapt/control/devices/camera.py +830 -0
- pyccapt-0.2.3/pyccapt/control/devices/edwards_tic.py +55 -0
- pyccapt-0.2.3/pyccapt/control/devices/email_send.py +310 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/devices/initialize_devices.py +171 -41
- pyccapt-0.2.3/pyccapt/control/devices/pfeiffer_gauges.py +252 -0
- pyccapt-0.2.3/pyccapt/control/devices/signal_generator.py +145 -0
- pyccapt-0.2.3/pyccapt/control/drs/__init__.py +1 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/drs/drs.py +48 -15
- pyccapt-0.2.3/pyccapt/control/gui/__init__.py +1 -0
- pyccapt-0.2.3/pyccapt/control/gui/gui_baking.py +516 -0
- pyccapt-0.2.3/pyccapt/control/gui/gui_cameras.py +1087 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/gui/gui_gates.py +109 -59
- pyccapt-0.2.3/pyccapt/control/gui/gui_laser_control.py +2247 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/gui/gui_main.py +1086 -415
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/gui/gui_pumps_vacuum.py +219 -270
- pyccapt-0.2.3/pyccapt/control/gui/gui_stage_control.py +800 -0
- pyccapt-0.2.3/pyccapt/control/gui/gui_visualization.py +1938 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/gui/main_parameters.py +2 -4
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/gui/process_coordinator.py +21 -5
- pyccapt-0.2.3/pyccapt/control/gui/tooltips.py +432 -0
- pyccapt-0.2.3/pyccapt/control/nkt_photonics/nktpbus_activate.py +49 -0
- pyccapt-0.2.3/pyccapt/control/nkt_photonics/nktpbus_switch.py +314 -0
- pyccapt-0.2.3/pyccapt/control/nkt_photonics/origamiClassCLI.py +404 -0
- pyccapt-0.2.3/pyccapt/control/smaract_mcs2/__init__.py +1 -0
- pyccapt-0.2.3/pyccapt/control/smaract_mcs2/mcs2_stage.py +436 -0
- pyccapt-0.2.3/pyccapt/control/tdc_roentdek/__init__.py +1 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/cobold_tool.py +2 -6
- pyccapt-0.2.3/pyccapt/control/tdc_roentdek/tdc_roentdek.py +205 -0
- pyccapt-0.2.3/pyccapt/control/tdc_surface_concept/__init__.py +1 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/scTDC.py +156 -188
- pyccapt-0.2.3/pyccapt/control/tdc_surface_concept/tdc_surface_concept.py +619 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/_APTAPI.py +61 -62
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/_error_codes.py +1 -1
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/core.py +373 -466
- pyccapt-0.2.3/pyccapt/control/thorlabs_apt/thorlab_motor.py +41 -0
- {pyccapt-0.2.2/pyccapt/calibration → pyccapt-0.2.3/pyccapt/control/usb_switch}/__init__.py +0 -1
- pyccapt-0.2.3/pyccapt/control/usb_switch/usb_switch.py +80 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_cameras.ui +3 -3
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_laser_control.ui +41 -122
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_main.ui +0 -14
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_pumps_vacuum.ui +1 -1
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_stage_control.ui +262 -102
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_visualization.ui +165 -22
- pyccapt-0.2.3/pyccapt/files/email_credentials.example.toml +48 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3/pyccapt.egg-info}/PKG-INFO +82 -2
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt.egg-info/SOURCES.txt +22 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt.egg-info/requires.txt +6 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyproject.toml +0 -9
- {pyccapt-0.2.2 → pyccapt-0.2.3}/setup.py +13 -0
- pyccapt-0.2.2/pyccapt/__init__.py +0 -3
- pyccapt-0.2.2/pyccapt/calibration/core/adaptive_residual_calibration.py +0 -492
- pyccapt-0.2.2/pyccapt/calibration/core/correction_models.py +0 -129
- pyccapt-0.2.2/pyccapt/calibration/core/mc_plot_background_helpers.py +0 -176
- pyccapt-0.2.2/pyccapt/calibration/data_tools/_raw_workflow_common.py +0 -672
- pyccapt-0.2.2/pyccapt/calibration/data_tools/_raw_workflow_surface_concept.py +0 -562
- pyccapt-0.2.2/pyccapt/calibration/data_tools/ato_tools.py +0 -168
- pyccapt-0.2.2/pyccapt/calibration/data_tools/dataset_path_qt.py +0 -52
- pyccapt-0.2.2/pyccapt/calibration/data_tools/plot_vline_draw.py +0 -130
- pyccapt-0.2.2/pyccapt/calibration/data_tools/run_dataset_path_qt.py +0 -12
- pyccapt-0.2.2/pyccapt/calibration/leap_tools/ccapt_tools.py +0 -197
- pyccapt-0.2.2/pyccapt/calibration/path_utils.py +0 -42
- pyccapt-0.2.2/pyccapt/calibration/reconstructions/rdf.py +0 -134
- pyccapt-0.2.2/pyccapt/config.toml +0 -88
- pyccapt-0.2.2/pyccapt/control/__main__.py +0 -48
- pyccapt-0.2.2/pyccapt/control/apt/__init__.py +0 -1
- pyccapt-0.2.2/pyccapt/control/apt/apt_exp_control_func.py +0 -218
- pyccapt-0.2.2/pyccapt/control/core/__init__.py +0 -1
- pyccapt-0.2.2/pyccapt/control/core/baking_loging.py +0 -218
- pyccapt-0.2.2/pyccapt/control/core/com_ports.py +0 -17
- pyccapt-0.2.2/pyccapt/control/core/hdf5_creator.py +0 -185
- pyccapt-0.2.2/pyccapt/control/core/loggi.py +0 -33
- pyccapt-0.2.2/pyccapt/control/core/runtime.py +0 -117
- pyccapt-0.2.2/pyccapt/control/core/share_variables.py +0 -382
- pyccapt-0.2.2/pyccapt/control/core/tof2mc_simple.py +0 -32
- pyccapt-0.2.2/pyccapt/control/devices/__init__.py +0 -1
- pyccapt-0.2.2/pyccapt/control/devices/camera.py +0 -348
- pyccapt-0.2.2/pyccapt/control/devices/edwards_tic.py +0 -43
- pyccapt-0.2.2/pyccapt/control/devices/email_send.py +0 -63
- pyccapt-0.2.2/pyccapt/control/devices/pfeiffer_gauges.py +0 -204
- pyccapt-0.2.2/pyccapt/control/devices/signal_generator.py +0 -105
- pyccapt-0.2.2/pyccapt/control/drs/__init__.py +0 -1
- pyccapt-0.2.2/pyccapt/control/gui/__init__.py +0 -1
- pyccapt-0.2.2/pyccapt/control/gui/gui_baking.py +0 -468
- pyccapt-0.2.2/pyccapt/control/gui/gui_cameras.py +0 -762
- pyccapt-0.2.2/pyccapt/control/gui/gui_laser_control.py +0 -1014
- pyccapt-0.2.2/pyccapt/control/gui/gui_stage_control.py +0 -294
- pyccapt-0.2.2/pyccapt/control/gui/gui_visualization.py +0 -1243
- pyccapt-0.2.2/pyccapt/control/nkt_photonics/nktpbus_activate.py +0 -40
- pyccapt-0.2.2/pyccapt/control/nkt_photonics/origamiClassCLI.py +0 -360
- pyccapt-0.2.2/pyccapt/control/tdc_roentdek/__init__.py +0 -1
- pyccapt-0.2.2/pyccapt/control/tdc_roentdek/tdc_roentdek.py +0 -179
- pyccapt-0.2.2/pyccapt/control/tdc_surface_concept/__init__.py +0 -1
- pyccapt-0.2.2/pyccapt/control/tdc_surface_concept/tdc_surface_concept.py +0 -439
- pyccapt-0.2.2/pyccapt/control/thorlabs_apt/thorlab_motor.py +0 -44
- pyccapt-0.2.2/pyccapt/control/usb_switch/usb_switch.py +0 -52
- {pyccapt-0.2.2 → pyccapt-0.2.3}/CHANGELOG.txt +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/LICENSE +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/MANIFEST.in +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/CALIBRATION.md +0 -0
- {pyccapt-0.2.2/pyccapt/control/thorlabs_apt → pyccapt-0.2.3/pyccapt/calibration}/__init__.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/clustering/__init__.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/background.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/exceptions.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/logging_library.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/core/spectrum_simulation.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/data_tools/__init__.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/__init__.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cameca_raw/README.md +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/leap_tools/cameca_raw/rhit_extract.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/mc/__init__.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/__init__.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reconstructions/specimen_builder.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/__init__.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/3000XHR_Leoben_21_14093_intersections.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/3000XHR_Leoben_21_14093_reference.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/3000XHR_Leoben_21_14093_triangles.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/4000XHR_Erlangen_56_4833_intersections.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/4000XHR_Erlangen_56_4833_reference.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/4000XHR_Erlangen_56_4833_triangles.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Leoben_5124_368_intersections.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Leoben_5124_368_reference.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Leoben_5124_368_triangles.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Oxford_5083_23091_intersections.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Oxford_5083_23091_reference.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/5000XR_Oxford_5083_23091_triangles.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/6000XR_Chalmers_6002_770_intersections.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/6000XR_Chalmers_6002_770_reference.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/calibration/reflectron_correction/data/presets/6000XR_Chalmers_6002_770_triangles.csv +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/__init__.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/drs/README.md +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/drs/drs_lib.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/drs/drs_lib.exp +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/drs/drs_lib.lib +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/electrode.toml +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/nkt_photonics/__init__.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/CoboldPC2011R5-2_Header-Template_1TDC8HP.lmf +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/README.md +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/global.cfg +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/source/CoboldPC2011R5-2_Header-Template_1TDC8HP.lmf +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/source/global.cfg +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/source/hptdc_driver_3.5.2_x64.lib +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/source/hptdc_driver_3.5.2_x86.lib +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/source/lmf2txt.exe +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_roentdek/wrapper_read_TDC8HP_x64.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/README.md +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/okFrontPanel.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/pthreadVC2.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/sc2ddld304_1b_tagrst.bit +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/scDeviceClass60.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/scTDC1.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/scTDC1.lib +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/scTDC_hdf50.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/tdc_surface_concept/tdc_gpx3.ini +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APT Server.chm +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APT.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APT.lib +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APTChopper.ocx +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APTLaser.ocx +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APTPZMotor.ocx +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APTQuad.ocx +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/APTTEC.ocx +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/GdiPlus.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Comms.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Core.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Logger.ocx +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Motor.ocx +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17NanoTrak.ocx +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Piezo.ocx +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17System.ocx +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17UIThread.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/MG17Utils.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/README.md +0 -0
- {pyccapt-0.2.2/pyccapt/control/usb_switch → pyccapt-0.2.3/pyccapt/control/thorlabs_apt}/__init__.py +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/thorlabs_apt/ftd2xx.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/control/usb_switch/USBaccessX64.dll +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/README.md +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_baking.ui +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_gates.ui +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/PyQt6_UI/gui_laser_control_special.ui +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/arrow.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/close_all.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/color_scheme.h5 +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/cryo_load_main_open.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/cryo_load_open.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/cryo_open.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/field_density_table.h5 +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/green-led-on.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/isotopeTable.h5 +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/led-orange.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/led-red-on.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/load_main_open.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/load_open.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/logo.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/logo2.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt/files/main_open.png +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt.egg-info/dependency_links.txt +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt.egg-info/entry_points.txt +0 -0
- {pyccapt-0.2.2 → pyccapt-0.2.3}/pyccapt.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|

|
|
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
|

|
|
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
|
-
|
|
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(
|
|
220
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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(
|
|
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(
|
|
282
|
+
centers.append(finite_points[candidate_index])
|
|
237
283
|
|
|
238
284
|
centers = np.asarray(centers, dtype=float)
|
|
239
|
-
labels = np.zeros(len(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
centers
|
|
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
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
|