pycat-napari 1.5.332__tar.gz → 1.5.334__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/CHANGELOG.md +73 -0
  2. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/PKG-INFO +1 -1
  3. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/pyproject.toml +1 -1
  4. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/dynamic_spatial_tools.py +18 -6
  5. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/vpt_tools.py +491 -14
  6. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/vpt_ui.py +132 -4
  7. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/.gitignore +0 -0
  8. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/CONTRIBUTING.md +0 -0
  9. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/LICENSE +0 -0
  10. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/README.md +0 -0
  11. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/THIRD_PARTY_LICENSES.txt +0 -0
  12. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/config/pycat-devbio-napari-env-arm-mac.yaml +0 -0
  13. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/config/pycat-devbio-napari-env-x86-windows.yaml +0 -0
  14. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/config/pycat-napari-env-arm-mac.yaml +0 -0
  15. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/config/pycat-napari-env-x86-windows.yaml +0 -0
  16. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/config/requirements-arm-mac.txt +0 -0
  17. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/config/requirements-base.txt +0 -0
  18. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/config/requirements-devbio-napari-arm-mac.txt +0 -0
  19. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/config/requirements-devbio-napari.txt +0 -0
  20. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/Makefile +0 -0
  21. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/audits/DEV_NOTES.md +0 -0
  22. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/audits/PyCAT_method_audit_2026-07-09.md +0 -0
  23. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/audits/PyCAT_napari_integration_audit_2026-07-09.md +0 -0
  24. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/logos/pycat_logo-2.png +0 -0
  25. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/logos/pycat_logo.png +0 -0
  26. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/logos/pycat_logo_1024.png +0 -0
  27. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/logos/pycat_logo_256.png +0 -0
  28. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/logos/pycat_logo_512.png +0 -0
  29. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/make.bat +0 -0
  30. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/requirements.txt +0 -0
  31. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/dark_mode.css +0 -0
  32. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/examples/Image 1 dapi.tif +0 -0
  33. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/examples/Image 1.tif +0 -0
  34. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/pycat_logo_512.png +0 -0
  35. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/screenshots/Viewer_and_menu_highlights.png +0 -0
  36. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/screenshots/cell_analyzer.png +0 -0
  37. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/screenshots/condensate_segmentation.png +0 -0
  38. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/screenshots/measuring_lines.png +0 -0
  39. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/screenshots/opened_image_in_viewer.png +0 -0
  40. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/screenshots/preprocessed_images.png +0 -0
  41. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_static/screenshots/save_and_clear_popup.png +0 -0
  42. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/_templates/autosummary/module.rst +0 -0
  43. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/api/index.rst +0 -0
  44. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/api/pycat.central_manager.rst +0 -0
  45. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/api/pycat.data.rst +0 -0
  46. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/api/pycat.file_io.rst +0 -0
  47. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/api/pycat.run_pycat.rst +0 -0
  48. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/api/pycat.toolbox.rst +0 -0
  49. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/api/pycat.ui.rst +0 -0
  50. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/api/pycat.utils.rst +0 -0
  51. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/conf.py +0 -0
  52. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/development/contributing.rst +0 -0
  53. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/development/index.rst +0 -0
  54. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/development/roadmap.rst +0 -0
  55. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/development/support.rst +0 -0
  56. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/features.rst +0 -0
  57. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/index.rst +0 -0
  58. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/installation.rst +0 -0
  59. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/usage/index.rst +0 -0
  60. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/source/usage/tutorials/nuclear_condensate_analysis_gui_tutorial.rst +0 -0
  61. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/docs/test.txt +0 -0
  62. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/notebooks/Synthtic Data Generation NB.ipynb +0 -0
  63. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/notebooks/int_truncated_dfs.ipynb +0 -0
  64. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/notebooks/pycat_plotting.ipynb +0 -0
  65. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/__init__.py +0 -0
  66. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/batch_processor.py +0 -0
  67. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/batch_step_registry.py +0 -0
  68. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/central_manager.py +0 -0
  69. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/data/__init__.py +0 -0
  70. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/data/data_modules.py +0 -0
  71. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/file_io/__init__.py +0 -0
  72. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/file_io/file_io.py +0 -0
  73. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/file_io/frap_io.py +0 -0
  74. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/file_io/metadata_extract.py +0 -0
  75. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/file_io/multidim_io.py +0 -0
  76. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/file_io/session_loader.py +0 -0
  77. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/fix_tifffile.py +0 -0
  78. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/icons/pycat_logo-2.png +0 -0
  79. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/icons/pycat_logo.png +0 -0
  80. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/icons/pycat_logo_1024.png +0 -0
  81. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/icons/pycat_logo_256.png +0 -0
  82. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/icons/pycat_logo_512.png +0 -0
  83. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/run_pycat.py +0 -0
  84. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/__init__.py +0 -0
  85. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/advanced_analysis_ui.py +0 -0
  86. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/analysis_plots.py +0 -0
  87. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/batch_roi_tools.py +0 -0
  88. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/benchmark_tools.py +0 -0
  89. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/brightfield_tools.py +0 -0
  90. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/brightfield_ui.py +0 -0
  91. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/clean_spot_detection_tools.py +0 -0
  92. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/condensate_physics_tools.py +0 -0
  93. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/condensate_physics_ui.py +0 -0
  94. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/contrast_cascade_tools.py +0 -0
  95. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/contrast_cascade_ui.py +0 -0
  96. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/correlation_func_analysis_tools.py +0 -0
  97. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/data_qc_tools.py +0 -0
  98. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/data_qc_ui.py +0 -0
  99. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/data_viz_tools.py +0 -0
  100. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/fd_curve_tools.py +0 -0
  101. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/fd_curve_ui.py +0 -0
  102. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/feature_analysis_tools.py +0 -0
  103. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/fft_bandpass_tools.py +0 -0
  104. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/fibril_tools.py +0 -0
  105. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/fibril_ui.py +0 -0
  106. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/frap_tools.py +0 -0
  107. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/frap_ui.py +0 -0
  108. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/fusion_tools.py +0 -0
  109. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/fusion_ui.py +0 -0
  110. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/gaussian_localization_tools.py +0 -0
  111. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/gpu_utils.py +0 -0
  112. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/image_processing_tools.py +0 -0
  113. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/intensity_profile_tools.py +0 -0
  114. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/invitro_bf_ui.py +0 -0
  115. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/invitro_fluor_ui.py +0 -0
  116. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/invitro_tools.py +0 -0
  117. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/label_and_mask_tools.py +0 -0
  118. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/layer_tools.py +0 -0
  119. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/molecular_counting_tools.py +0 -0
  120. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/morphological_complexity_tools.py +0 -0
  121. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/nb_tools.py +0 -0
  122. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/nb_ui.py +0 -0
  123. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/numba_utils.py +0 -0
  124. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/obj_based_coloc_analysis_tools.py +0 -0
  125. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/organizational_metrics_tools.py +0 -0
  126. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/partition_enrichment_tools.py +0 -0
  127. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/pipeline_diagnostic_tools.py +0 -0
  128. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/pipeline_snr_tools.py +0 -0
  129. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/pixel_wise_corr_analysis_tools.py +0 -0
  130. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/segmentation_tools.py +0 -0
  131. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/spatial_acf_tools.py +0 -0
  132. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/spatial_metrology_tools.py +0 -0
  133. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/spatial_metrology_ui.py +0 -0
  134. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/spatial_randomness_tools.py +0 -0
  135. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/spida_tools.py +0 -0
  136. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/spida_ui.py +0 -0
  137. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/temperature_tools.py +0 -0
  138. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/temperature_ui.py +0 -0
  139. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/temporal_enhancement_tools.py +0 -0
  140. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/timeseries_condensate_tools.py +0 -0
  141. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/timeseries_invitro_fluor_ui.py +0 -0
  142. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/timeseries_invitro_tools.py +0 -0
  143. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/topology_tools.py +0 -0
  144. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/trackmate_bridge.py +0 -0
  145. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/ts_cache_manager.py +0 -0
  146. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/ts_cellpose_tools.py +0 -0
  147. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/two_channel_coloc_tools.py +0 -0
  148. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/video_export_tools.py +0 -0
  149. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/zstack_segmentation_tools.py +0 -0
  150. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/toolbox/zstack_segmentation_ui.py +0 -0
  151. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/__init__.py +0 -0
  152. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/coordinate_readout.py +0 -0
  153. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/field_status.py +0 -0
  154. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/ui_analysis_mixin.py +0 -0
  155. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/ui_diagnostics_mixin.py +0 -0
  156. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/ui_filtering_mixin.py +0 -0
  157. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/ui_imageops_mixin.py +0 -0
  158. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/ui_labels_mixin.py +0 -0
  159. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/ui_modules.py +0 -0
  160. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/ui_segmentation_mixin.py +0 -0
  161. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/ui_utils.py +0 -0
  162. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/ui/workflow_checklist.py +0 -0
  163. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/utils/__init__.py +0 -0
  164. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/utils/channel_naming.py +0 -0
  165. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/utils/general_utils.py +0 -0
  166. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/utils/logging_utils.py +0 -0
  167. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/src/pycat/utils/math_utils.py +0 -0
  168. {pycat_napari-1.5.332 → pycat_napari-1.5.334}/tests/README.md +0 -0
@@ -4,6 +4,79 @@ All notable changes to PyCAT-Napari will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.5.334] - 2026-07-10
8
+ ### Fixed — VPT linker shattered stable beads into short tracks (the core linking problem)
9
+ - **Root cause 1 — gap-frame off-by-one.** The greedy linker's viability AND
10
+ expiry checks used `t - last_frame <= max_gap_frames`, which at `gap=0` rejects
11
+ even the immediately-previous frame — collapsing every detection into its own
12
+ length-1 "track" (and hanging/crashing the GUI when it then tried to build a
13
+ Tracks layer from tens of thousands of singletons). Fixed to
14
+ `<= max_gap_frames + 1` so `gap=0` links consecutive frames, `gap=1` bridges one
15
+ missing frame, etc. The Bayesian linker had the same off-by-one in its expiry
16
+ check only (inconsistent with its own viability filter, which was already
17
+ correct) — also fixed. Verified on real data: a confirmed stable bead present in
18
+ 1000/1000 frames now links into ONE 1000-frame track instead of shattering.
19
+ - **Root cause 2 — max linking distance too tight, now auto-derived from bead
20
+ motion.** The dominant problem: the default linking distance (~0.4 µm) was below
21
+ the beads' own frame-to-frame jitter tail (they move up to ~340 nm/frame), so it
22
+ clipped real motion and broke stable beads into short fragments that cannot
23
+ support the MSD measurement window. **New `estimate_linking_distance_um()`
24
+ derives a physically-grounded distance WITHOUT linking any tracks**, via a
25
+ short-window MAX-projection of the stack: the projected bead width broadened
26
+ beyond the single-frame PSF width gives the per-frame displacement scale
27
+ (`motion_σ = √(σ²_proj − σ²_psf)`), and the distance is `k × motion_σ` (k margin,
28
+ default 2.5), capped at the bead footprint so it can't grab neighbours. It is
29
+ viscosity-adaptive (slow beads → tight, fast beads → loose) and needs no
30
+ provisional linking pass. Auto-filled into the Step-4 linker field after Detect
31
+ Beads (shown and editable; anti-black-box), with the margin `k` exposed under a
32
+ new "Show advanced linking options" expander. Validated on real data: derives
33
+ 0.58 µm (per-frame motion ~232 nm × 2.5), and linking there yields full
34
+ 1000-frame tracks — 10% of tracks now span ≥80% of the movie (was ~0%),
35
+ i.e. measurement-grade trajectories.
36
+ - **GUI crash guard.** If a link still comes out degenerate (>2000 tracks and
37
+ >90% single-frame), PyCAT warns and skips building the Tracks layer instead of
38
+ freezing.
39
+
40
+ ### Changed — track-length histogram pops out
41
+ - The linker track-length histogram now opens as a dockable "Track lengths" panel
42
+ (reused across relinks) instead of sitting inline in the Step-4 form.
43
+
44
+ ## [1.5.333] - 2026-07-10
45
+ ### Added — VPT detection-variant staging + track-length histogram
46
+ - **Detection-variant staging framework.** `detect_beads_stack` gained a
47
+ `detection_variant` argument (default `'baseline'` = the 1.5.329-validated
48
+ path, byte-identical). New detection/classification approaches are opt-in
49
+ variants routed through their own branches, so the validated ~8.325-through-
50
+ TrackMate path stays selectable and any regression is a clean one-arg revert. A
51
+ `compare_detection_variants()` harness runs two variants on the same stack and
52
+ reports the classification diff, and the chosen variant is recorded on the
53
+ output DataFrame's attrs. This is the safety net for the detection rework —
54
+ every proposed change is A/B-measured against baseline before it is trusted.
55
+ - **`ring_merge` variant** (`dedup_detections_ring_merge`) — sigma-scaled merge
56
+ radius that folds DIM Airy-ring fragments into their BRIGHT centre while keeping
57
+ two genuinely-bright nearby beads as two. Built and kept in the codebase but
58
+ **not surfaced in the widget and flagged as needing validation data with
59
+ resolved Airy rings**: A/B on the current bead data showed it is a near no-op
60
+ there (beads well-separated, blob_log already ~one detection per bead). Reach it
61
+ via `detection_variant='ring_merge'`.
62
+ - **`hot_pixel_reject` variant** (`build_hot_pixel_mask` + harsher on-pixel NCC
63
+ gate) — identifies FIXED sensor hot/dead pixels from the stack's *temporal*
64
+ statistics (scene-independent: hot pixels are flat in time, tstd~3-4; beads are
65
+ variable, tstd~40-50), then applies a STRICTER acceptance test to detections
66
+ landing on them rather than a flat veto — so a real bead drifting over a hot/
67
+ dead pixel still survives on its template evidence. Validated correct and safe
68
+ (every confirmed bead survived, including one adjacent to a hot pixel). Nearly a
69
+ no-op on the current clean fluorescence data (~18 hot pixels found, blob_log
70
+ barely fires on them); earns its place on data/modes that turn hot pixels into
71
+ recurring false detections. Reach it via `detection_variant='hot_pixel_reject'`.
72
+ - **Track-length histogram in the linker widget.** Step 4 (Link Trajectories) now
73
+ shows an embedded histogram of trajectory lengths (frames per track) after each
74
+ link. A healthy link piles mass toward long tracks; a fragmentation-prone linker
75
+ shows a spike of very short tracks. The title reports the track count and the
76
+ fraction spanning ≥½ the movie, and a dashed line marks the median — an
77
+ at-a-glance linker-quality check. Fails safe if matplotlib-Qt embedding is
78
+ unavailable.
79
+
7
80
  ## [1.5.332] - 2026-07-10
8
81
  ### Fixed — VPT classifier green↔yellow flicker on bright, well-matched beads
9
82
  - **A bright, high-NCC bead no longer flips singlet↔out_of_plane frame-to-frame.**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycat-napari
3
- Version: 1.5.332
3
+ Version: 1.5.334
4
4
  Summary: Python Condensate Analysis Toolbox - A napari-based tool for biomolecular condensate analysis
5
5
  Project-URL: Homepage, https://github.com/BanerjeeLab-repertoire/pycat-napari
6
6
  Project-URL: Repository, https://github.com/BanerjeeLab-repertoire/pycat-napari
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pycat-napari"
7
- version = "1.5.332"
7
+ version = "1.5.334"
8
8
  description = "Python Condensate Analysis Toolbox - A napari-based tool for biomolecular condensate analysis"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12,<3.13"
@@ -332,9 +332,12 @@ def link_trajectories_bayesian(
332
332
  assigned_dets.add(c)
333
333
  # else: death (r < n_viable, c >= n_curr) — track ends naturally
334
334
 
335
- # Expire tracks not seen for too long
335
+ # Expire tracks not seen for too long. Must match the viability filter
336
+ # above (t - last_frame <= max_gap_frames + 1); using the bare
337
+ # max_gap_frames here expired tracks one frame too early, so a track was
338
+ # admitted as viable next frame but could already have been dropped.
336
339
  active = {tid: info for tid, info in active.items()
337
- if t - info['last_frame'] <= max_gap_frames}
340
+ if t - info['last_frame'] <= max_gap_frames + 1}
338
341
 
339
342
  # ── Gap closing: second pass ──────────────────────────────────────────
340
343
  if max_gap_frames > 0:
@@ -470,9 +473,17 @@ def link_trajectories(
470
473
  curr = df[df['frame'] == t].copy()
471
474
  curr_pts = curr[['y_um', 'x_um']].values
472
475
 
473
- # Find which active tracks are still linkable (within gap window)
476
+ # Find which active tracks are still linkable (within gap window).
477
+ # max_gap_frames = number of MISSING frames tolerated, so a track last
478
+ # seen at frame f is linkable at frame t when (t - f) - 1 <= max_gap,
479
+ # i.e. t - f <= max_gap + 1. gap=0 therefore links CONSECUTIVE frames
480
+ # (t - f == 1), gap=1 bridges one missing frame (t - f == 2), etc.
481
+ # (Previously this used `t - f <= max_gap`, an off-by-one that made
482
+ # gap=0 reject even the immediately-previous frame — collapsing every
483
+ # track to length 1. Beads present in consecutive frames must link at
484
+ # gap=0.)
474
485
  viable = {tid: info for tid, info in active_tracks.items()
475
- if t - info[2] <= max_gap_frames}
486
+ if t - info[2] <= max_gap_frames + 1}
476
487
 
477
488
  if not viable or len(curr_pts) == 0:
478
489
  # Start new tracks for all detections this frame
@@ -516,9 +527,10 @@ def link_trajectories(
516
527
  df.at[df_idx, 'y_um'], df.at[df_idx, 'x_um'], t)
517
528
  next_track += 1
518
529
 
519
- # Remove expired tracks
530
+ # Remove expired tracks (same gap semantics as the viability filter:
531
+ # keep a track while it could still be linked at the next frame).
520
532
  active_tracks = {tid: info for tid, info in active_tracks.items()
521
- if t - info[2] <= max_gap_frames}
533
+ if t - info[2] <= max_gap_frames + 1}
522
534
 
523
535
  return df
524
536