microeye 2.4.3__tar.gz → 2.4.4__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.
- {microeye-2.4.3/src/microeye.egg-info → microeye-2.4.4}/PKG-INFO +6 -6
- {microeye-2.4.3 → microeye-2.4.4}/README.md +5 -5
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/_version.py +1 -1
- microeye-2.4.4/src/microEye/analysis/clustering/dbscan.py +459 -0
- microeye-2.4.4/src/microEye/analysis/clustering/plot.py +1141 -0
- microeye-2.4.4/src/microEye/analysis/clustering/rulers.py +361 -0
- microeye-2.4.4/src/microEye/analysis/file_system.py +394 -0
- microeye-2.4.4/src/microEye/analysis/fitting/aim.py +836 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/processing.py +60 -34
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/spline.py +1 -1
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/results.py +141 -81
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/multi_viewer.py +85 -272
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/processing/frc.py +1 -1
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/rendering/base.py +20 -13
- microeye-2.4.4/src/microEye/analysis/tools/batch.py +420 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/registration.py +17 -5
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/utils/__init__.py +1 -1
- microeye-2.4.4/src/microEye/analysis/utils/windows.py +40 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/viewer/images.py +119 -80
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/viewer/localizations.py +288 -351
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/viewer/psf.py +2 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/camera_options.py +15 -1
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/camera_panel.py +1 -1
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/mieye/acquisition_manager.py +133 -119
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/mieye/miEye.py +10 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/pycromanager/devices.py +1 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/widgets/scan_acquisition.py +24 -8
- microeye-2.4.4/src/microEye/tools/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/display.py +27 -1
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/expandable_groupbox.py +10 -2
- {microeye-2.4.3 → microeye-2.4.4/src/microeye.egg-info}/PKG-INFO +6 -6
- {microeye-2.4.3 → microeye-2.4.4}/src/microeye.egg-info/SOURCES.txt +7 -1
- microeye-2.4.3/src/microEye/analysis/fitting/clustering.py +0 -50
- microeye-2.4.3/src/microEye/analysis/utils/windows.py +0 -19
- {microeye-2.4.3 → microeye-2.4.4}/LICENSE +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/MANIFEST.in +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/pyproject.toml +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/setup.cfg +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/setup.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/checklist_dialog.py +0 -0
- {microeye-2.4.3/src/microEye/analysis/fitting → microeye-2.4.4/src/microEye/analysis/clustering}/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/cmosMaps.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/filters/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/filters/base.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/filters/spatial.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/filters/temporal.py +0 -0
- {microeye-2.4.3/src/microEye/analysis/tools → microeye-2.4.4/src/microEye/analysis/fitting}/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/fit.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/nena.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/phasor_fit.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/extract.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/rubost_mean.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/stats/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/stats/core.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/stats/curve_fit.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/stats/io.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/stats/slope_fit.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/temp.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/psf/test.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUfunctions.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUmleFit_LM.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUsplineLib.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/CPU/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUfunctions.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_EMCCD.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_sCMOS.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUsplineLib.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/GPU/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/constants.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/pyfit3Dcspline/mainfunctions.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/rcc.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/results_stats.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/fitting/tardis.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/processing/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/rendering/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/rendering/cloud.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/rendering/core.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/rendering/volumetric.py +0 -0
- {microeye-2.4.3/src/microEye/hardware/cams/basler → microeye-2.4.4/src/microEye/analysis/tools}/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/common.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/dark_cal/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/dark_cal/analysis.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/dark_cal/compare.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/dark_cal/constants.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/dark_cal/core.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/dark_cal/io.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/dark_cal/plotting.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/kymograms.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/photon_cal/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/photon_cal/analysis.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/photon_cal/compare.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/photon_cal/constants.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/photon_cal/core.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/photon_cal/io.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/photon_cal/models.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/photon_cal/plotting.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/photon_cal/table.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/roi_selectors.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/tools/sphere_cal.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/utils/coordinates.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/utils/images.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/viewer/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/viewer/image_options_widget.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/viewer/layers_widget.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/analysis/viewer/volume.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/__init__.py +0 -0
- {microeye-2.4.3/src/microEye/hardware/misc → microeye-2.4.4/src/microEye/hardware/cams/basler}/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/basler/basler_cam.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/basler/basler_panel.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/camera_calibration.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/camera_list.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/dummy/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/dummy/dummy_panel.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/jobs.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/line_profiler.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/linescan/IR_Cam.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/linescan/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/micam.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/pco/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/pco/enums.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/pco/pco_cam.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/pco/pco_panel.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/peak/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/peak/peak_camera.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/peak/peak_panel.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/pycromanager/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/pycromanager/pycro_cam.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/pycromanager/pycro_panel.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/shortcuts.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/thorlabs/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/thorlabs/thorlabs.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/thorlabs/thorlabs_panel.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/ueye/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/ueye/ueye_camera.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/ueye/ueye_panel.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/vimba/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/vimba/vimba_cam.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/cams/vimba/vimba_panel.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/device.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/lasers/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/lasers/io_matchbox.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/lasers/io_params.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/lasers/io_single_laser.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/lasers/laser_relay.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/mieye/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/mieye/devices_manager.py +0 -0
- {microeye-2.4.3/src/microEye/hardware/protocols → microeye-2.4.4/src/microEye/hardware/misc}/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/misc/acquisition_view.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/misc/reglo.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/misc/temp.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/port_config.py +0 -0
- {microeye-2.4.3/src/microEye/hardware/stages → microeye-2.4.4/src/microEye/hardware/protocols}/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/protocols/actions.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/protocols/actions_items.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/protocols/designer.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/protocols/scene_manager.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/protocols/serialization.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/pycromanager/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/pycromanager/core.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/pycromanager/enums.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/pycromanager/headless.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/pycromanager/utils.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/pycromanager/widgets/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/pycromanager/widgets/bridges.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/pycromanager/widgets/headless_manager.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/pycromanager/widgets/headless_options.py +0 -0
- {microeye-2.4.3/src/microEye/hardware/stages/stabilization → microeye-2.4.4/src/microEye/hardware/stages}/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/baseDevice.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/device.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/deviceID.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/devicePort.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/deviceStatus.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/devicesView.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/ellDevices.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/messageUpdater.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/motorInfo.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/stage.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/elliptec/test.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/kinesis/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/kinesis/kdc101/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/kinesis/kdc101/enums.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/kinesis/kdc101/factory.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/kinesis/kdc101/kdc101.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/kinesis/kinesis.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/manager.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/piezon_concept/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/piezon_concept/foc.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/pycromanager/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/pycromanager/core.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/smaract/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/smaract/mcs2.py +0 -0
- {microeye-2.4.3/src/microEye/icons → microeye-2.4.4/src/microEye/hardware/stages/stabilization}/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/stabilization/controller.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/stabilization/methods.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/stabilizer.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/stage.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/stages/view.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/widgets/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/widgets/controller.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/widgets/devices.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/widgets/focusWidget.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/widgets/indicator.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/widgets/qlist_slider.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/hardware/widgets/slides.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/1024.png +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/128.png +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/16.png +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/24.png +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/256.png +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/32.png +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/48.png +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/512.png +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/64.png +0 -0
- {microeye-2.4.3/src/microEye/tools → microeye-2.4.4/src/microEye/icons}/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/close.svg +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/mieye.png +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/min.svg +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/icons/viewer.png +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/images/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/images/handlers.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/images/uImage.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/images/zarr.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/launcher.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/qt.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/tools/color.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/tools/microscopy.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/enum_encoder.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/gui_helper.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/hid_utils/__init__.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/hid_utils/controller.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/hid_utils/device.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/hid_utils/enums.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/hid_utils/utils.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/labelled_slider.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/metadata.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/metadata_tree.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/micro_launcher.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/parameter_tree.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/pyqt2mplt.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/pyscripting.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/retry_exec.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/start_gui.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microEye/utils/thread_worker.py +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microeye.egg-info/dependency_links.txt +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microeye.egg-info/entry_points.txt +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microeye.egg-info/requires.txt +0 -0
- {microeye-2.4.3 → microeye-2.4.4}/src/microeye.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microeye
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.4
|
|
4
4
|
Summary: A python toolkit for fluorescence microscopy that features hardware control, data analysis and vizualization for super-resolution single-molecule localization microscopy and single-partical tracking.
|
|
5
5
|
Home-page: https://github.com/samhitech/microEye
|
|
6
6
|
Author: Mohammad Nour Alsamsam
|
|
@@ -72,11 +72,11 @@ The **`microEye`** is a Python toolkit for fluorescence microscopy that supports
|
|
|
72
72
|
This toolkit is compatible with the [hardware](#hardware) used in our microscope. For further details, refer to the [miEye microscope paper](https://doi.org/10.1016/j.ohx.2022.e00368) and [OSF project](http://doi.org/10.17605/osf.io/j2fqy).
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
|
-
__ ____ ____ ___ ____
|
|
76
|
-
/ |/ (_)__________ / __/_ _____ _ __|_ |/ / /
|
|
77
|
-
/ /|_/ / / __/ __/ _ \/ _// // / -_) | |/ / __//_ _/
|
|
78
|
-
/_/ /_/_/\__/_/ \___/___/\_, /\__/ |___/____(_)_/(_)
|
|
79
|
-
/___/
|
|
75
|
+
__ ____ ____ ___ ____ ____
|
|
76
|
+
/ |/ (_)__________ / __/_ _____ _ __|_ |/ / // / /
|
|
77
|
+
/ /|_/ / / __/ __/ _ \/ _// // / -_) | |/ / __//_ _/_ _/
|
|
78
|
+
/_/ /_/_/\__/_/ \___/___/\_, /\__/ |___/____(_)_/(_)_/
|
|
79
|
+
/___/
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
<!--  -->
|
|
@@ -7,11 +7,11 @@ The **`microEye`** is a Python toolkit for fluorescence microscopy that supports
|
|
|
7
7
|
This toolkit is compatible with the [hardware](#hardware) used in our microscope. For further details, refer to the [miEye microscope paper](https://doi.org/10.1016/j.ohx.2022.e00368) and [OSF project](http://doi.org/10.17605/osf.io/j2fqy).
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
__ ____ ____ ___ ____
|
|
11
|
-
/ |/ (_)__________ / __/_ _____ _ __|_ |/ / /
|
|
12
|
-
/ /|_/ / / __/ __/ _ \/ _// // / -_) | |/ / __//_ _/
|
|
13
|
-
/_/ /_/_/\__/_/ \___/___/\_, /\__/ |___/____(_)_/(_)
|
|
14
|
-
/___/
|
|
10
|
+
__ ____ ____ ___ ____ ____
|
|
11
|
+
/ |/ (_)__________ / __/_ _____ _ __|_ |/ / // / /
|
|
12
|
+
/ /|_/ / / __/ __/ _ \/ _// // / -_) | |/ / __//_ _/_ _/
|
|
13
|
+
/_/ /_/_/\__/_/ \___/___/\_, /\__/ |___/____(_)_/(_)_/
|
|
14
|
+
/___/
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
<!--  -->
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
version_info = (2, 4,
|
|
1
|
+
version_info = (2, 4, 4)
|
|
2
2
|
__version__ = '.'.join(map(str, version_info))
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
import numba as nb
|
|
6
|
+
import numpy as np
|
|
7
|
+
from sklearn.cluster import DBSCAN
|
|
8
|
+
from sklearn.preprocessing import StandardScaler
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@nb.njit(cache=True)
|
|
14
|
+
def geometric_median(points, max_iter=128, tol=1e-6, eps=1e-12):
|
|
15
|
+
guess = np.median(points, axis=0)
|
|
16
|
+
for _ in range(max_iter):
|
|
17
|
+
try:
|
|
18
|
+
deltas = points - guess
|
|
19
|
+
distances = np.linalg.norm(deltas, axis=1)
|
|
20
|
+
weights = 1.0 / np.maximum(distances, eps)
|
|
21
|
+
new_guess = np.sum(points * weights[:, None], axis=0) / np.sum(weights)
|
|
22
|
+
if np.linalg.norm(new_guess - guess) < tol:
|
|
23
|
+
return new_guess
|
|
24
|
+
guess = new_guess
|
|
25
|
+
except Exception as e:
|
|
26
|
+
logger.error(f'Error in geometric_median: {e}')
|
|
27
|
+
break
|
|
28
|
+
return guess
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@nb.njit(fastmath=True, cache=True)
|
|
32
|
+
def geometric_median_numba(points, max_iter=128, tol=1e-6, eps=1e-12):
|
|
33
|
+
n_points, n_dims = points.shape
|
|
34
|
+
|
|
35
|
+
guess = np.empty(n_dims, dtype=points.dtype)
|
|
36
|
+
for d in range(n_dims):
|
|
37
|
+
guess[d] = np.median(points[:, d])
|
|
38
|
+
|
|
39
|
+
new_guess = np.empty(n_dims, dtype=points.dtype)
|
|
40
|
+
|
|
41
|
+
# Iterative Weiszfeld's algorithm loop
|
|
42
|
+
for _ in range(max_iter):
|
|
43
|
+
total_weight = 0.0
|
|
44
|
+
|
|
45
|
+
# Reset new_guess array to zero for current iteration accumulation
|
|
46
|
+
for d in range(n_dims):
|
|
47
|
+
new_guess[d] = 0.0
|
|
48
|
+
|
|
49
|
+
# Compute weights and accumulate values using flat loops
|
|
50
|
+
for i in range(n_points):
|
|
51
|
+
dist_sq = 0.0
|
|
52
|
+
for d in range(n_dims):
|
|
53
|
+
diff = points[i, d] - guess[d]
|
|
54
|
+
dist_sq += diff * diff
|
|
55
|
+
|
|
56
|
+
dist = np.sqrt(dist_sq)
|
|
57
|
+
if dist < eps:
|
|
58
|
+
dist = eps
|
|
59
|
+
|
|
60
|
+
weight = 1.0 / dist
|
|
61
|
+
total_weight += weight
|
|
62
|
+
|
|
63
|
+
for d in range(n_dims):
|
|
64
|
+
new_guess[d] += points[i, d] * weight
|
|
65
|
+
|
|
66
|
+
if total_weight == 0.0:
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
# Finalize the step calculation
|
|
70
|
+
for d in range(n_dims):
|
|
71
|
+
new_guess[d] /= total_weight
|
|
72
|
+
|
|
73
|
+
# Check convergence using Euclidean distance
|
|
74
|
+
delta_sq = 0.0
|
|
75
|
+
for d in range(n_dims):
|
|
76
|
+
diff = new_guess[d] - guess[d]
|
|
77
|
+
delta_sq += diff * diff
|
|
78
|
+
|
|
79
|
+
if np.sqrt(delta_sq) < tol:
|
|
80
|
+
return new_guess
|
|
81
|
+
|
|
82
|
+
# Update current guess manually via value copies
|
|
83
|
+
for d in range(n_dims):
|
|
84
|
+
guess[d] = new_guess[d]
|
|
85
|
+
|
|
86
|
+
return guess
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@nb.njit(fastmath=True, cache=True)
|
|
90
|
+
def huber_center(points, delta=2.0, max_iter=64, tol=1e-5):
|
|
91
|
+
'''
|
|
92
|
+
Calculates the spatial robust center using M-estimation with a Huber loss.
|
|
93
|
+
Eliminates the single-pixel snapping artifact of the geometric median.
|
|
94
|
+
|
|
95
|
+
delta: Distance threshold (in nm) where the loss switches from L2 to L1.
|
|
96
|
+
Set this to roughly 1 to 2 times your expected cluster precision.
|
|
97
|
+
'''
|
|
98
|
+
n_points, n_dims = points.shape
|
|
99
|
+
|
|
100
|
+
# Safety Check: If empty array or corrupted inputs passed, exit early
|
|
101
|
+
if n_points == 0:
|
|
102
|
+
return np.zeros(n_dims, dtype=points.dtype)
|
|
103
|
+
|
|
104
|
+
# Initialize guess with standard mean
|
|
105
|
+
guess = np.empty(n_dims, dtype=points.dtype)
|
|
106
|
+
for d in range(n_dims):
|
|
107
|
+
guess[d] = np.mean(points[:, d])
|
|
108
|
+
|
|
109
|
+
new_guess = np.empty(n_dims, dtype=points.dtype)
|
|
110
|
+
|
|
111
|
+
for _ in range(max_iter):
|
|
112
|
+
total_weight = 0.0
|
|
113
|
+
for d in range(n_dims):
|
|
114
|
+
new_guess[d] = 0.0
|
|
115
|
+
|
|
116
|
+
for i in range(n_points):
|
|
117
|
+
dist_sq = 0.0
|
|
118
|
+
for d in range(n_dims):
|
|
119
|
+
diff = points[i, d] - guess[d]
|
|
120
|
+
dist_sq += diff * diff
|
|
121
|
+
dist = np.sqrt(dist_sq)
|
|
122
|
+
|
|
123
|
+
if dist <= delta:
|
|
124
|
+
weight = 1.0
|
|
125
|
+
else:
|
|
126
|
+
# Numba friendly approach: ensure dist can NEVER be zero here
|
|
127
|
+
# since dist > delta and delta > 0, dist is physically > 0,
|
|
128
|
+
# but we add an explicit fallback for safety.
|
|
129
|
+
safe_dist = dist if dist > 1e-12 else 1e-12
|
|
130
|
+
weight = delta / safe_dist
|
|
131
|
+
|
|
132
|
+
total_weight += weight
|
|
133
|
+
for d in range(n_dims):
|
|
134
|
+
new_guess[d] += points[i, d] * weight
|
|
135
|
+
|
|
136
|
+
# FIXED: Wrap the division completely inside a strict safety check
|
|
137
|
+
# to prevent Numba from pre-optimizing the division pathway.
|
|
138
|
+
if total_weight > 1e-12:
|
|
139
|
+
for d in range(n_dims):
|
|
140
|
+
new_guess[d] /= total_weight
|
|
141
|
+
else:
|
|
142
|
+
# Fallback if weights collapse to zero (e.g. all points are NaN/Inf)
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
# Check Convergence
|
|
146
|
+
delta_sq = 0.0
|
|
147
|
+
for d in range(n_dims):
|
|
148
|
+
diff = new_guess[d] - guess[d]
|
|
149
|
+
delta_sq += diff * diff
|
|
150
|
+
|
|
151
|
+
if np.sqrt(delta_sq) < tol:
|
|
152
|
+
return new_guess
|
|
153
|
+
|
|
154
|
+
for d in range(n_dims):
|
|
155
|
+
guess[d] = new_guess[d]
|
|
156
|
+
|
|
157
|
+
return guess
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class CenteringMode(Enum):
|
|
161
|
+
CRLB_WEIGHTED_MEAN = 'crlb_weighted_mean'
|
|
162
|
+
MEAN = 'mean'
|
|
163
|
+
GEOMETRIC_MEDIAN = 'geometric_median'
|
|
164
|
+
HUBER_CENTER = 'huber_center'
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def get_center(
|
|
168
|
+
x: np.ndarray,
|
|
169
|
+
y: np.ndarray,
|
|
170
|
+
z: np.ndarray | None,
|
|
171
|
+
center_mode: CenteringMode = CenteringMode.CRLB_WEIGHTED_MEAN,
|
|
172
|
+
crlb_x: np.ndarray | None = None,
|
|
173
|
+
crlb_y: np.ndarray | None = None,
|
|
174
|
+
eps: float = 1e-12,
|
|
175
|
+
):
|
|
176
|
+
'''
|
|
177
|
+
Computes the center of clusters based on the specified centering mode.
|
|
178
|
+
|
|
179
|
+
*Methods*
|
|
180
|
+
|
|
181
|
+
- CRLB_WEIGHTED_MEAN: Computes the center using a weighted mean based on the
|
|
182
|
+
Cramer-Rao Lower Bound (CRLB) variances for x and y coordinates.
|
|
183
|
+
- GEOMETRIC_MEDIAN: Computes the center as the geometric median of the points.
|
|
184
|
+
- HUBER_CENTER: Computes the center using M-estimation with a Huber loss function.
|
|
185
|
+
- MEAN: Computes the center as the arithmetic mean of the points.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
-----------
|
|
189
|
+
x, y, z: np.ndarray
|
|
190
|
+
Arrays of x, y, z coordinates of localizations.
|
|
191
|
+
center_mode: CenteringMode
|
|
192
|
+
The method to compute the center of clusters.
|
|
193
|
+
crlb_x, crlb_y: np.ndarray, optional
|
|
194
|
+
Arrays of CRLB variance values for x and y coordinates.
|
|
195
|
+
Required if center_mode is CenteringMode.CRLB_WEIGHTED_MEAN.
|
|
196
|
+
eps: float
|
|
197
|
+
A small value to avoid division by zero when computing weights.
|
|
198
|
+
(default: 1e-12)
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
--------
|
|
202
|
+
mu_x, mu_y, mu_z: np.ndarray
|
|
203
|
+
'''
|
|
204
|
+
if (
|
|
205
|
+
center_mode == CenteringMode.CRLB_WEIGHTED_MEAN
|
|
206
|
+
and crlb_x is not None
|
|
207
|
+
and crlb_y is not None
|
|
208
|
+
):
|
|
209
|
+
wx = 1.0 / np.maximum(crlb_x, eps)
|
|
210
|
+
wy = 1.0 / np.maximum(crlb_y, eps)
|
|
211
|
+
mu_x = np.sum(wx * x) / np.sum(wx)
|
|
212
|
+
mu_y = np.sum(wy * y) / np.sum(wy)
|
|
213
|
+
mu_z = np.mean(z) if z is not None else None
|
|
214
|
+
|
|
215
|
+
# 1. Calculate variance (squared error) for X and Y independently
|
|
216
|
+
var_x_com = 1.0 / np.sum(1.0 / np.maximum(crlb_x, eps))
|
|
217
|
+
var_y_com = 1.0 / np.sum(1.0 / np.maximum(crlb_y, eps))
|
|
218
|
+
|
|
219
|
+
# 2. Combine them in quadrature to get true 2D radial uncertainty
|
|
220
|
+
sigma_com = np.sqrt(var_x_com + var_y_com)
|
|
221
|
+
|
|
222
|
+
elif center_mode == CenteringMode.GEOMETRIC_MEDIAN:
|
|
223
|
+
if z is not None:
|
|
224
|
+
mu_x, mu_y, mu_z = geometric_median_numba(np.column_stack((x, y, z)))
|
|
225
|
+
else:
|
|
226
|
+
mu_x, mu_y = geometric_median_numba(np.column_stack((x, y)))
|
|
227
|
+
mu_z = None
|
|
228
|
+
|
|
229
|
+
var_x = np.mean((x - mu_x) ** 2)
|
|
230
|
+
var_y = np.mean((y - mu_y) ** 2)
|
|
231
|
+
|
|
232
|
+
# sqrt(pi/2) factor converts from 1D std to 2D radial std
|
|
233
|
+
sigma_com = 1.253 * np.sqrt(var_x + var_y) / np.sqrt(len(x))
|
|
234
|
+
|
|
235
|
+
elif center_mode == CenteringMode.HUBER_CENTER:
|
|
236
|
+
if z is not None:
|
|
237
|
+
mu_x, mu_y, mu_z = huber_center(np.column_stack((x, y, z)))
|
|
238
|
+
else:
|
|
239
|
+
mu_x, mu_y = huber_center(np.column_stack((x, y)))
|
|
240
|
+
mu_z = None
|
|
241
|
+
|
|
242
|
+
# Median Absolute Deviation (MAD)
|
|
243
|
+
mad_x = 1.4826 * np.median(np.abs(x - mu_x)) / np.sqrt(len(x))
|
|
244
|
+
mad_y = 1.4826 * np.median(np.abs(y - mu_y)) / np.sqrt(len(y))
|
|
245
|
+
|
|
246
|
+
sigma_com = np.sqrt(mad_x**2 + mad_y**2)
|
|
247
|
+
|
|
248
|
+
else:
|
|
249
|
+
mu_x = np.mean(x)
|
|
250
|
+
mu_y = np.mean(y)
|
|
251
|
+
mu_z = np.mean(z) if z is not None else None
|
|
252
|
+
|
|
253
|
+
var_x = np.mean((x - mu_x) ** 2)
|
|
254
|
+
var_y = np.mean((y - mu_y) ** 2)
|
|
255
|
+
|
|
256
|
+
# Standard error of the mean
|
|
257
|
+
sigma_com = np.sqrt(var_x + var_y) / np.sqrt(len(x))
|
|
258
|
+
|
|
259
|
+
return mu_x, mu_y, mu_z, sigma_com
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass
|
|
263
|
+
class ClusterCenterResult:
|
|
264
|
+
x: np.ndarray
|
|
265
|
+
y: np.ndarray
|
|
266
|
+
z: np.ndarray | None
|
|
267
|
+
labels: np.ndarray
|
|
268
|
+
intensity: np.ndarray | None
|
|
269
|
+
crlb_x: np.ndarray | None
|
|
270
|
+
crlb_y: np.ndarray | None
|
|
271
|
+
cx: np.ndarray
|
|
272
|
+
cy: np.ndarray
|
|
273
|
+
cz: np.ndarray | None
|
|
274
|
+
c_sigma: np.ndarray | None
|
|
275
|
+
c_label: np.ndarray
|
|
276
|
+
n_points: np.ndarray
|
|
277
|
+
rejected: int
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def get_centers(
|
|
281
|
+
x: np.ndarray,
|
|
282
|
+
y: np.ndarray,
|
|
283
|
+
z: np.ndarray | None,
|
|
284
|
+
labels: np.ndarray,
|
|
285
|
+
intensity: np.ndarray | None = None,
|
|
286
|
+
crlb_x: np.ndarray | None = None,
|
|
287
|
+
crlb_y: np.ndarray | None = None,
|
|
288
|
+
center_mode: CenteringMode = CenteringMode.CRLB_WEIGHTED_MEAN,
|
|
289
|
+
eps: float = 1e-12,
|
|
290
|
+
rejection_threshold: float | None = None,
|
|
291
|
+
min_points: int | None = None,
|
|
292
|
+
max_points: int | None = None,
|
|
293
|
+
subtract_com: bool = False,
|
|
294
|
+
**kwargs,
|
|
295
|
+
) -> ClusterCenterResult | None:
|
|
296
|
+
try:
|
|
297
|
+
# Filter out noise (label < 0) safely
|
|
298
|
+
valid_mask = labels >= 0
|
|
299
|
+
x = x[valid_mask]
|
|
300
|
+
y = y[valid_mask]
|
|
301
|
+
z = z[valid_mask] if z is not None else None
|
|
302
|
+
intensity = intensity[valid_mask] if intensity is not None else None
|
|
303
|
+
labels = labels[valid_mask]
|
|
304
|
+
|
|
305
|
+
if crlb_x is not None:
|
|
306
|
+
crlb_x = np.asarray(crlb_x, dtype=np.float64)[valid_mask]
|
|
307
|
+
if crlb_y is not None:
|
|
308
|
+
crlb_y = np.asarray(crlb_y, dtype=np.float64)[valid_mask]
|
|
309
|
+
|
|
310
|
+
if len(labels) == 0:
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
_arg_sort = np.argsort(labels)
|
|
314
|
+
x = x[_arg_sort]
|
|
315
|
+
y = y[_arg_sort]
|
|
316
|
+
z = z[_arg_sort] if z is not None else None
|
|
317
|
+
intensity = intensity[_arg_sort] if intensity is not None else None
|
|
318
|
+
if crlb_x is not None:
|
|
319
|
+
crlb_x = crlb_x[_arg_sort]
|
|
320
|
+
if crlb_y is not None:
|
|
321
|
+
crlb_y = crlb_y[_arg_sort]
|
|
322
|
+
|
|
323
|
+
labels = labels[_arg_sort]
|
|
324
|
+
|
|
325
|
+
label_ids = np.cumsum(np.bincount(labels.astype(np.int64)))
|
|
326
|
+
|
|
327
|
+
unique_labels = np.unique(labels.astype(np.int64))
|
|
328
|
+
|
|
329
|
+
# array to hold the computed centers for each cluster
|
|
330
|
+
cx = []
|
|
331
|
+
cy = []
|
|
332
|
+
cz = [] if z is not None else None
|
|
333
|
+
c_sigma = []
|
|
334
|
+
c_labels = []
|
|
335
|
+
n_points = []
|
|
336
|
+
|
|
337
|
+
rejected = []
|
|
338
|
+
|
|
339
|
+
for i in unique_labels:
|
|
340
|
+
s = slice(label_ids[i - 1] if i > 0 else 0, label_ids[i])
|
|
341
|
+
|
|
342
|
+
if len(x[s]) == 0:
|
|
343
|
+
logger.debug('Cluster %d has no points. Skipping.', i)
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
x_cluster = x[s]
|
|
347
|
+
y_cluster = y[s]
|
|
348
|
+
z_cluster = z[s] if z is not None else None
|
|
349
|
+
|
|
350
|
+
mu_x, mu_y, mu_z, sigma_com = get_center(
|
|
351
|
+
x_cluster,
|
|
352
|
+
y_cluster,
|
|
353
|
+
z_cluster,
|
|
354
|
+
center_mode=center_mode,
|
|
355
|
+
crlb_x=crlb_x[s] if crlb_x is not None else None,
|
|
356
|
+
crlb_y=crlb_y[s] if crlb_y is not None else None,
|
|
357
|
+
eps=eps,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Reject clusters with large uncertainty
|
|
361
|
+
if (
|
|
362
|
+
(rejection_threshold is not None and sigma_com > rejection_threshold)
|
|
363
|
+
or (min_points is not None and len(x_cluster) < min_points)
|
|
364
|
+
or (max_points is not None and len(x_cluster) > max_points)
|
|
365
|
+
):
|
|
366
|
+
rejected.append(i)
|
|
367
|
+
continue
|
|
368
|
+
|
|
369
|
+
cx.append(mu_x)
|
|
370
|
+
cy.append(mu_y)
|
|
371
|
+
if cz is not None:
|
|
372
|
+
cz.append(mu_z)
|
|
373
|
+
c_sigma.append(sigma_com)
|
|
374
|
+
c_labels.append(i)
|
|
375
|
+
n_points.append(len(x_cluster))
|
|
376
|
+
|
|
377
|
+
if subtract_com:
|
|
378
|
+
x[s] -= mu_x
|
|
379
|
+
y[s] -= mu_y
|
|
380
|
+
if z is not None:
|
|
381
|
+
z[s] -= mu_z
|
|
382
|
+
|
|
383
|
+
if len(rejected) > 0:
|
|
384
|
+
logger.warning(
|
|
385
|
+
'Rejected %d clusters due to uncertainty or insufficient points.',
|
|
386
|
+
len(rejected),
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
valid_mask = np.isin(labels, rejected, invert=True)
|
|
390
|
+
x = x[valid_mask]
|
|
391
|
+
y = y[valid_mask]
|
|
392
|
+
intensity = intensity[valid_mask] if intensity is not None else None
|
|
393
|
+
labels = labels[valid_mask]
|
|
394
|
+
crlb_x = crlb_x[valid_mask] if crlb_x is not None else None
|
|
395
|
+
crlb_y = crlb_y[valid_mask] if crlb_y is not None else None
|
|
396
|
+
if z is not None:
|
|
397
|
+
z = z[valid_mask]
|
|
398
|
+
|
|
399
|
+
return ClusterCenterResult(
|
|
400
|
+
x=x,
|
|
401
|
+
y=y,
|
|
402
|
+
z=z,
|
|
403
|
+
labels=labels,
|
|
404
|
+
intensity=intensity,
|
|
405
|
+
crlb_x=crlb_x,
|
|
406
|
+
crlb_y=crlb_y,
|
|
407
|
+
cx=np.array(cx),
|
|
408
|
+
cy=np.array(cy),
|
|
409
|
+
cz=np.array(cz) if cz is not None else None,
|
|
410
|
+
c_sigma=np.array(c_sigma),
|
|
411
|
+
c_label=np.array(c_labels),
|
|
412
|
+
n_points=np.array(n_points),
|
|
413
|
+
rejected=len(rejected),
|
|
414
|
+
)
|
|
415
|
+
except Exception as e:
|
|
416
|
+
logger.error(f'Error in get_centers: {e}')
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def perform_dbscan_clustering(X, Y, Z, eps=50, min_samples=2, **kwargs):
|
|
421
|
+
'''
|
|
422
|
+
Performs DBSCAN clustering on 3D localization data.
|
|
423
|
+
|
|
424
|
+
Parameters:
|
|
425
|
+
------------
|
|
426
|
+
X, Y, Z: array-like
|
|
427
|
+
Arrays of x, y, z coordinates of localizations.
|
|
428
|
+
eps: float, optional
|
|
429
|
+
The maximum distance between two samples for them to be considered
|
|
430
|
+
as in the same neighborhood. Default is 50.
|
|
431
|
+
min_samples: int, optional
|
|
432
|
+
The number of samples in a neighborhood for a point to be considered
|
|
433
|
+
a core point. Default is 2.
|
|
434
|
+
**kwargs: additional keyword arguments for DBSCAN.
|
|
435
|
+
'''
|
|
436
|
+
VECTORS = np.column_stack((X, Y, Z) if Z is not None else (X, Y))
|
|
437
|
+
|
|
438
|
+
if kwargs.pop('scale_data', False):
|
|
439
|
+
std = np.std(VECTORS)
|
|
440
|
+
if std > 0:
|
|
441
|
+
VECTORS /= std
|
|
442
|
+
eps /= std # Scale eps accordingly
|
|
443
|
+
else:
|
|
444
|
+
logger.warning('Standard deviation of data is zero. Skipping scaling.')
|
|
445
|
+
|
|
446
|
+
# 2. Initialize and fit DBSCAN
|
|
447
|
+
# eps: maximum distance between two samples
|
|
448
|
+
# min_samples: number of samples in a neighborhood for a point to be a core point
|
|
449
|
+
dbscan_model = DBSCAN(eps=eps, min_samples=min_samples, **kwargs)
|
|
450
|
+
dbscan_model.fit(VECTORS)
|
|
451
|
+
|
|
452
|
+
# 3. Retrieve labels (-1 indicates noise/outliers)
|
|
453
|
+
labels = dbscan_model.labels_
|
|
454
|
+
unique = np.unique(labels).shape[0] - 1
|
|
455
|
+
logger.info(
|
|
456
|
+
'Cluster labels: %d (%d Points)', unique, labels[labels != -1].shape[0]
|
|
457
|
+
) # Log only non-noise labels
|
|
458
|
+
|
|
459
|
+
return labels
|