bioimage-cpp 0.2.0__tar.gz → 0.4.0__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 (291) hide show
  1. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/AGENTS.md +2 -2
  2. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/MIGRATION_GUIDE.md +25 -0
  3. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/PKG-INFO +11 -1
  4. bioimage_cpp-0.4.0/README.md +20 -0
  5. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/flow/PERFORMANCE_NOTES.md +84 -14
  6. bioimage_cpp-0.4.0/development/graph/benchmark_accumulate_labels.py +82 -0
  7. bioimage_cpp-0.4.0/development/graph/benchmark_bfs.py +70 -0
  8. bioimage_cpp-0.4.0/development/segmentation/benchmark_mutex_watershed.py +89 -0
  9. bioimage_cpp-0.4.0/development/utils/benchmark_union_find.py +70 -0
  10. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/grid.hxx +10 -0
  11. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/profile.hxx +6 -1
  12. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/flow/flow_density.hxx +4 -1
  13. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/detail.hxx +3 -2
  14. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/breadth_first_search.hxx +30 -9
  15. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/feature_accumulation.hxx +5 -9
  16. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/label_accumulation.hxx +6 -12
  17. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/greedy_additive.hxx +3 -1
  18. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/greedy_additive.hxx +3 -1
  19. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/node_label_projection.hxx +2 -11
  20. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/proposal_generators/greedy_additive_multicut.hxx +18 -1
  21. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/proposal_generators/watershed.hxx +3 -0
  22. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/label_multiset/multiset.hxx +12 -2
  23. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/non_maximum_distance_suppression.hxx +3 -1
  24. bioimage_cpp-0.4.0/include/bioimage_cpp/run_length.hxx +48 -0
  25. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/segmentation/mutex_watershed.hxx +5 -0
  26. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/segmentation/semantic_mutex_watershed.hxx +4 -0
  27. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/filters.cxx +16 -16
  28. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/segmentation.cxx +1 -5
  29. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/util.cxx +53 -4
  30. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/utils.cxx +45 -0
  31. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/__init__.py +19 -3
  32. bioimage_cpp-0.4.0/src/bioimage_cpp/_version.py +1 -0
  33. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/utils.py +52 -0
  34. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/distance/test_non_maximum_distance_suppression.py +14 -0
  35. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/label_multiset/test_downsample.py +18 -0
  36. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/test_util_union_find.py +30 -0
  37. bioimage_cpp-0.4.0/tests/test_utils_compute_rle.py +101 -0
  38. bioimage_cpp-0.2.0/README.md +0 -10
  39. bioimage_cpp-0.2.0/src/bioimage_cpp/_version.py +0 -1
  40. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/.github/workflows/docs.yml +0 -0
  41. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/.github/workflows/tests.yml +0 -0
  42. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/.github/workflows/wheels.yml +0 -0
  43. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/.gitignore +0 -0
  44. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/CMakeLists.txt +0 -0
  45. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/LICENSE +0 -0
  46. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/affinities/check_compute_affinities.py +0 -0
  47. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/affinities/check_compute_embedding_distances.py +0 -0
  48. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/distance/benchmark.py +0 -0
  49. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/distance/check_non_maximum_distance_suppression.py +0 -0
  50. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/filters/PERFORMANCE_NOTES.md +0 -0
  51. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/filters/_bench_utils.py +0 -0
  52. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/filters/benchmark.py +0 -0
  53. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/filters/check_parity.py +0 -0
  54. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/flow/_reference_impl.py +0 -0
  55. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/flow/benchmark.py +0 -0
  56. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/flow/check_flow_density.py +0 -0
  57. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/flow/create_test_data.py +0 -0
  58. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/_grid_affinity_compatibility.py +0 -0
  59. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/_rag_compatibility.py +0 -0
  60. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/PERFORMANCE_NOTES.md +0 -0
  61. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/_compatibility.py +0 -0
  62. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/check_edge_weighted.py +0 -0
  63. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/check_gasp.py +0 -0
  64. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/check_mala.py +0 -0
  65. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/check_node_and_edge_weighted.py +0 -0
  66. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/diagnose.py +0 -0
  67. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/check_grid_affinity_edges.py +0 -0
  68. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/check_grid_affinity_lifted_edges.py +0 -0
  69. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/check_rag_2d.py +0 -0
  70. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/check_rag_3d.py +0 -0
  71. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/PERFORMANCE_NOTES.md +0 -0
  72. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/_compatibility.py +0 -0
  73. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/check_fusion_move.py +0 -0
  74. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/check_greedy_additive.py +0 -0
  75. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/check_kernighan_lin.py +0 -0
  76. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/evaluate_solvers.py +0 -0
  77. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/repro_lifted_edges_segfault.py +0 -0
  78. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/multicut/PERFORMANCE_NOTES.md +0 -0
  79. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/multicut/_compatibility.py +0 -0
  80. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_chained.py +0 -0
  81. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_decomposer.py +0 -0
  82. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_fusion_move.py +0 -0
  83. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_greedy_additive.py +0 -0
  84. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_greedy_fixation.py +0 -0
  85. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_kernighan_lin.py +0 -0
  86. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/multicut/evaluate_solvers.py +0 -0
  87. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/mutex_watershed/check_mutex_clustering.py +0 -0
  88. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/graph/mutex_watershed/check_semantic_mutex_clustering.py +0 -0
  89. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/label_multiset/benchmark.py +0 -0
  90. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/local_maxima/benchmark.py +0 -0
  91. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/PERFORMANCE_NOTES.md +0 -0
  92. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/_label_equivalence.py +0 -0
  93. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/_mutex_watershed_equivalence.py +0 -0
  94. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/_semantic_mws_equivalence.py +0 -0
  95. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/_watershed_equivalence.py +0 -0
  96. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/_watershed_from_affinities_equivalence.py +0 -0
  97. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_label_2d.py +0 -0
  98. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_label_3d.py +0 -0
  99. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_mutex_watershed_2d.py +0 -0
  100. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_mutex_watershed_3d.py +0 -0
  101. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_relabel_sequential.py +0 -0
  102. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_semantic_mutex_watershed_2d.py +0 -0
  103. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_semantic_mutex_watershed_3d.py +0 -0
  104. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_watershed_2d.py +0 -0
  105. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_watershed_3d.py +0 -0
  106. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_watershed_from_affinities_2d.py +0 -0
  107. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/segmentation/check_watershed_from_affinities_3d.py +0 -0
  108. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/transformation/PERFORMANCE_NOTES.md +0 -0
  109. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/transformation/check_affine.py +0 -0
  110. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/utils/_mesh_smoothing_reference.py +0 -0
  111. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/development/utils/benchmark_mesh_smoothing.py +0 -0
  112. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/examples/segmentation/_lifted_problem.py +0 -0
  113. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/examples/segmentation/lifted_multicut_from_affinities.py +0 -0
  114. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/examples/segmentation/multicut_from_affinities.py +0 -0
  115. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/examples/segmentation/mutex_watershed.py +0 -0
  116. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/examples/segmentation/serialize_grid_lifted_problem.py +0 -0
  117. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/examples/segmentation/serialize_lifted_problem.py +0 -0
  118. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/examples/segmentation/watershed.py +0 -0
  119. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/affinities/compute_affinities.hxx +0 -0
  120. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/affinities/compute_embedding_distances.hxx +0 -0
  121. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/array_view.hxx +0 -0
  122. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/blocking.hxx +0 -0
  123. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/edge_hash.hxx +0 -0
  124. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/indexed_heap.hxx +0 -0
  125. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/mutex_storage.hxx +0 -0
  126. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/relabel.hxx +0 -0
  127. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/semantic_labels.hxx +0 -0
  128. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/threading.hxx +0 -0
  129. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/distance_transform.hxx +0 -0
  130. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/filters/convolve.hxx +0 -0
  131. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/filters/eigenvalues.hxx +0 -0
  132. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/filters/gaussian.hxx +0 -0
  133. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/filters/kernel.hxx +0 -0
  134. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/agglomerative_clustering.hxx +0 -0
  135. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/cluster_policy_base.hxx +0 -0
  136. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/edge_weighted.hxx +0 -0
  137. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/gasp.hxx +0 -0
  138. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/mala.hxx +0 -0
  139. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/node_and_edge_weighted.hxx +0 -0
  140. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration.hxx +0 -0
  141. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/connected_components.hxx +0 -0
  142. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/detail/fusion_contract.hxx +0 -0
  143. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/edge_weighted_watershed.hxx +0 -0
  144. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/grid_edge_projection.hxx +0 -0
  145. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/grid_features.hxx +0 -0
  146. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/grid_graph.hxx +0 -0
  147. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_from_affinities.hxx +0 -0
  148. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/detail.hxx +0 -0
  149. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/fusion_move.hxx +0 -0
  150. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/kernighan_lin.hxx +0 -0
  151. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/lifted_from_node_labels.hxx +0 -0
  152. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/objective.hxx +0 -0
  153. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut.hxx +0 -0
  154. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/detail.hxx +0 -0
  155. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/fusion_move.hxx +0 -0
  156. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/greedy_fixation.hxx +0 -0
  157. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/kernighan_lin.hxx +0 -0
  158. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/objective.hxx +0 -0
  159. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut.hxx +0 -0
  160. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/mutex_watershed/clustering.hxx +0 -0
  161. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/mutex_watershed/semantic.hxx +0 -0
  162. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/mutex_watershed.hxx +0 -0
  163. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/proposal_generator.hxx +0 -0
  164. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/rag_coordinates.hxx +0 -0
  165. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/region_adjacency_graph.hxx +0 -0
  166. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/undirected_graph.hxx +0 -0
  167. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/label_multiset/downsample.hxx +0 -0
  168. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/label_multiset/from_labels.hxx +0 -0
  169. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/label_multiset/merger.hxx +0 -0
  170. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/label_multiset/read_subset.hxx +0 -0
  171. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/mesh_smoothing.hxx +0 -0
  172. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/overlap.hxx +0 -0
  173. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/segmentation/connected_components.hxx +0 -0
  174. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/segmentation/relabel_sequential.hxx +0 -0
  175. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/segmentation/watershed.hxx +0 -0
  176. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/take_dict.hxx +0 -0
  177. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/transformation/affine.hxx +0 -0
  178. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/util/union_find.hxx +0 -0
  179. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/pyproject.toml +0 -0
  180. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/affinities.cxx +0 -0
  181. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/affinities.hxx +0 -0
  182. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/blocking.cxx +0 -0
  183. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/blocking.hxx +0 -0
  184. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/distance.cxx +0 -0
  185. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/distance.hxx +0 -0
  186. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/filters.hxx +0 -0
  187. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/flow.cxx +0 -0
  188. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/flow.hxx +0 -0
  189. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/graph.cxx +0 -0
  190. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/graph.hxx +0 -0
  191. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/ground_truth.cxx +0 -0
  192. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/ground_truth.hxx +0 -0
  193. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/label_multiset.cxx +0 -0
  194. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/label_multiset.hxx +0 -0
  195. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/module.cxx +0 -0
  196. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/segmentation.hxx +0 -0
  197. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/transformation.cxx +0 -0
  198. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/transformation.hxx +0 -0
  199. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/util.hxx +0 -0
  200. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bindings/utils.hxx +0 -0
  201. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/_data.py +0 -0
  202. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/affinities/__init__.py +0 -0
  203. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/affinities/compute_affinities.py +0 -0
  204. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/affinities/compute_embedding_distances.py +0 -0
  205. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/distance/__init__.py +0 -0
  206. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/distance/_distance.py +0 -0
  207. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/filters/__init__.py +0 -0
  208. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/filters/_filters.py +0 -0
  209. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/flow/__init__.py +0 -0
  210. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/flow/_flow.py +0 -0
  211. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/__init__.py +0 -0
  212. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/_external.py +0 -0
  213. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/_shared.py +0 -0
  214. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/agglomeration.py +0 -0
  215. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/features/__init__.py +0 -0
  216. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/lifted_multicut/__init__.py +0 -0
  217. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/multicut/__init__.py +0 -0
  218. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/mutex_watershed.py +0 -0
  219. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/label_multiset/__init__.py +0 -0
  220. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/segmentation/__init__.py +0 -0
  221. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/segmentation/label.py +0 -0
  222. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/segmentation/mutex_watershed.py +0 -0
  223. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/segmentation/relabel_sequential.py +0 -0
  224. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/segmentation/watershed.py +0 -0
  225. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/transformation/__init__.py +0 -0
  226. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/transformation/_transformation.py +0 -0
  227. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/cpp/segmentation/mutex_watershed.cxx +0 -0
  228. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/cpp/segmentation/semantic_mutex_watershed.cxx +0 -0
  229. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/src/cpp/take_dict.cxx +0 -0
  230. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/affinities/test_compute_affinities.py +0 -0
  231. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/affinities/test_compute_embedding_distances.py +0 -0
  232. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/distance/test_distance_transform.py +0 -0
  233. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/__init__.py +0 -0
  234. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/_helpers.py +0 -0
  235. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/test_edge_weighted.py +0 -0
  236. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/test_gasp.py +0 -0
  237. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/test_mala.py +0 -0
  238. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/test_node_and_edge_weighted.py +0 -0
  239. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/__init__.py +0 -0
  240. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/_helpers.py +0 -0
  241. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/conftest.py +0 -0
  242. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_external_problem.py +0 -0
  243. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_fusion_move.py +0 -0
  244. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_greedy_additive.py +0 -0
  245. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_kernighan_lin.py +0 -0
  246. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_lifted_edges_from_node_labels.py +0 -0
  247. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_objective.py +0 -0
  248. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/__init__.py +0 -0
  249. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/_helpers.py +0 -0
  250. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/conftest.py +0 -0
  251. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_chain_decomposer.py +0 -0
  252. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_external_problem.py +0 -0
  253. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_fusion_move.py +0 -0
  254. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_greedy_additive.py +0 -0
  255. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_greedy_fixation.py +0 -0
  256. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_kernighan_lin.py +0 -0
  257. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_objective.py +0 -0
  258. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_proposal_generators.py +0 -0
  259. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_breadth_first_search.py +0 -0
  260. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_connected_components.py +0 -0
  261. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_edge_weighted_watershed.py +0 -0
  262. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_grid_affinity_multicut_integration.py +0 -0
  263. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_grid_features.py +0 -0
  264. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_grid_graph.py +0 -0
  265. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_mutex_watershed_graph.py +0 -0
  266. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_node_label_projection.py +0 -0
  267. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_rag_accumulate_labels.py +0 -0
  268. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_rag_coordinates.py +0 -0
  269. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_rag_features.py +0 -0
  270. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_rag_lifted_features.py +0 -0
  271. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_rag_multicut_integration.py +0 -0
  272. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_region_adjacency_graph.py +0 -0
  273. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_semantic_mutex_watershed_graph.py +0 -0
  274. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/graph/test_undirected_graph.py +0 -0
  275. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/label_multiset/__init__.py +0 -0
  276. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/label_multiset/test_against_nifty.py +0 -0
  277. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/label_multiset/test_merger.py +0 -0
  278. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/label_multiset/test_read_subset.py +0 -0
  279. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_label.py +0 -0
  280. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_mutex_watershed.py +0 -0
  281. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_relabel_sequential.py +0 -0
  282. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_semantic_mutex_watershed.py +0 -0
  283. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_watershed.py +0 -0
  284. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_watershed_from_affinities.py +0 -0
  285. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/test_blocking.py +0 -0
  286. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/test_filters.py +0 -0
  287. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/test_flow.py +0 -0
  288. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/test_ground_truth_overlap.py +0 -0
  289. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/test_transformation.py +0 -0
  290. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/test_utils_mesh_smoothing.py +0 -0
  291. {bioimage_cpp-0.2.0 → bioimage_cpp-0.4.0}/tests/test_utils_take_dict.py +0 -0
@@ -24,10 +24,10 @@ Required in the environment: `scikit-build-core`, `cmake>=3.21`, `ninja`, `nanob
24
24
 
25
25
  Before introducing union-finds, priority queues, edge hashing, stride math, threading helpers, or label relabeling, check `include/bioimage_cpp/detail/`:
26
26
 
27
- - `detail/union_find.hxx` — `UnionFind` (path compression + union by rank). `find`, `merge`, `merge_to`, `unite_roots`.
27
+ - `util/union_find.hxx` — `UnionFind` (namespace `bioimage_cpp::util`, not `detail`; path compression + union by rank). `find`, `merge`, `merge_to`, `unite_roots`.
28
28
  - `detail/indexed_heap.hxx` — addressable max-heap with mutable priorities. `DenseIndexedHeap` (vector-backed locator, integer keys in `[0, N)`) and `SparseIndexedHeap` (hashmap-backed, arbitrary keys).
29
29
  - `detail/edge_hash.hxx` — `Edge`, `edge_key`, `EdgeHash` for hashing unordered node pairs.
30
- - `detail/grid.hxx` — `c_order_strides`, `valid_offset_target`, `is_valid_grid_edge` for row-major grid offsets.
30
+ - `detail/grid.hxx` — `number_of_elements`, `c_order_strides`, `valid_offset_target`, `is_valid_grid_edge` for row-major grid shapes/offsets.
31
31
  - `detail/relabel.hxx` — `dense_relabel` to map arbitrary labels onto `[0, k)` preserving first-occurrence order.
32
32
  - `detail/threading.hxx` — `normalize_thread_count`, `parallel_for_chunks(n_threads, n_items, chunk)`.
33
33
 
@@ -770,6 +770,31 @@ Notes:
770
770
  - Every value in the input must be present in the mapping.
771
771
  - Non-contiguous inputs are copied before entering C++.
772
772
 
773
+ ### Run-Length Encoding
774
+
775
+ `nifty.tools.computeRLE` computes the COCO-style binary run-length encoding of an
776
+ array. Use `bic.utils.compute_rle`:
777
+
778
+ ```python
779
+ import nifty.tools as nt
780
+
781
+ mask = np.array([[0, 0, 1], [1, 1, 0], [0, 1, 1]], dtype=np.uint8)
782
+
783
+ rle = nt.computeRLE(mask) # nifty: Python list [2, 3, 2, 2]
784
+ rle = bic.utils.compute_rle(mask) # bioimage-cpp: np.int64 array([2, 3, 2, 2])
785
+ ```
786
+
787
+ Notes:
788
+
789
+ - The array is flattened in **C-order** and interpreted as binary
790
+ (zero vs. nonzero). Counts always start with a run of zeros and then
791
+ alternate; a leading `0` is emitted when the first element is nonzero.
792
+ - Supported input dtypes are `bool`, `uint8`, `uint16`, `uint32`, `uint64`,
793
+ `int32`, and `int64`.
794
+ - **Behavioral difference:** nifty returns a Python `list`; `bioimage-cpp`
795
+ returns a 1-D NumPy `int64` array.
796
+ - Non-contiguous inputs are copied before entering C++.
797
+
773
798
  ### Edge-Weighted Watershed
774
799
 
775
800
  Nifty:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: bioimage-cpp
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Dependency-light C++ bioimage analysis algorithms with Python bindings
5
5
  Author: bioimage-cpp contributors
6
6
  License: MIT
@@ -31,6 +31,11 @@ Description-Content-Type: text/markdown
31
31
 
32
32
  # bioimage-cpp
33
33
 
34
+ [![Documentation](https://img.shields.io/badge/docs-pdoc-blue)](https://computational-cell-analytics.github.io/bioimage-cpp/)
35
+ [![Build Status](https://github.com/computational-cell-analytics/bioimage-cpp/actions/workflows/tests.yml/badge.svg)](https://github.com/computational-cell-analytics/bioimage-cpp/actions/workflows/tests.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/bioimage-cpp)](https://pypi.org/project/bioimage-cpp/)
37
+ [![conda-forge](https://img.shields.io/conda/vn/conda-forge/bioimage-cpp)](https://anaconda.org/conda-forge/bioimage-cpp)
38
+
34
39
  Image processing and segmentation functionality in C++ with light-weight python bindings through nanobind and minimal dependencies to enable distribution via pip.
35
40
 
36
41
  The `bioimage_cpp` python library can be installed via pip:
@@ -38,4 +43,9 @@ The `bioimage_cpp` python library can be installed via pip:
38
43
  pip install bioimage-cpp
39
44
  ```
40
45
 
46
+ Or via conda-forge:
47
+ ```bash
48
+ conda install -c conda-forge bioimage-cpp
49
+ ```
50
+
41
51
  Please refer to [the documentation](https://computational-cell-analytics.github.io/bioimage-cpp/) for details.
@@ -0,0 +1,20 @@
1
+ # bioimage-cpp
2
+
3
+ [![Documentation](https://img.shields.io/badge/docs-pdoc-blue)](https://computational-cell-analytics.github.io/bioimage-cpp/)
4
+ [![Build Status](https://github.com/computational-cell-analytics/bioimage-cpp/actions/workflows/tests.yml/badge.svg)](https://github.com/computational-cell-analytics/bioimage-cpp/actions/workflows/tests.yml)
5
+ [![PyPI](https://img.shields.io/pypi/v/bioimage-cpp)](https://pypi.org/project/bioimage-cpp/)
6
+ [![conda-forge](https://img.shields.io/conda/vn/conda-forge/bioimage-cpp)](https://anaconda.org/conda-forge/bioimage-cpp)
7
+
8
+ Image processing and segmentation functionality in C++ with light-weight python bindings through nanobind and minimal dependencies to enable distribution via pip.
9
+
10
+ The `bioimage_cpp` python library can be installed via pip:
11
+ ```bash
12
+ pip install bioimage-cpp
13
+ ```
14
+
15
+ Or via conda-forge:
16
+ ```bash
17
+ conda install -c conda-forge bioimage-cpp
18
+ ```
19
+
20
+ Please refer to [the documentation](https://computational-cell-analytics.github.io/bioimage-cpp/) for details.
@@ -199,6 +199,65 @@ wheel still loads on SSE2-only CPUs. Expected speedup at 1T: ~20–30 %, less
199
199
  at 8T due to closer-to-bandwidth saturation. Estimated effort: half a day
200
200
  plus a careful microbenchmark of `vpgatherdd` cost on the target CPUs.
201
201
 
202
+ ## Interleaved (channel-last) layout + hand-written AVX2 FMA (rolled back, 2026-06-12)
203
+
204
+ This implemented the two top "remaining" ideas below and measured them. **Net
205
+ result: no win; small regression. Rolled back.** Documented here so the avenue
206
+ is not re-attempted without new information.
207
+
208
+ The idea, in two composable stages:
209
+
210
+ 1. **Channel-last (interleaved) flow buffer.** The input is channel-first
211
+ (`channels[axis] = flow.data + axis*n_pixels`), so the `D` components at one
212
+ position are gathers from regions `n_pixels` floats apart — in theory `D`
213
+ distinct cache lines per corner. A one-time `O(N)` transpose into a
214
+ `std::vector<float>` where a voxel's components sit at `[v*VS + axis]` makes
215
+ each corner touch one line and yield all `D` components. `compute_corners`
216
+ is unchanged (its offset is already the voxel flat index); only the sampler
217
+ multiplies by the voxel stride `VS`.
218
+ 2. **Hand-written AVX2+FMA sampler for 3D** on that interleaved buffer, with
219
+ `VS = 4` padding so each corner loads as one 128-bit vector. Lane-wise FMA
220
+ accumulates the weighted `D`-vector across the 8 corners — **no gather**
221
+ (the rejected approach above). Runtime-dispatched via
222
+ `__builtin_cpu_supports`, per-function `__attribute__((target("avx2,fma")))`,
223
+ scalar fallback for MSVC/arm64/non-AVX2. No CMake/global-arch-flag change.
224
+
225
+ The codegen was confirmed ideal via `objdump`: 8× `vfmadd132ps` each fusing a
226
+ 128-bit load (`(%rdi,%rax,1)`) with a `vbroadcastss` weight, zero gather
227
+ instructions. So the SIMD path was real and optimal — and still no faster than
228
+ scalar.
229
+
230
+ Measurements (3D fixture, warm, min of 8 runs, same session, same machine as
231
+ the headline numbers):
232
+
233
+ ```
234
+ variant 1T min 8T min
235
+ channel-first (baseline) 5.33 s 1.357 s
236
+ interleaved VS=3 (no pad, scalar) 5.44 s 1.46 s
237
+ interleaved VS=4 (scalar) 5.56 s 1.42 s
238
+ interleaved VS=4 + AVX2 FMA 5.58 s 1.46 s
239
+ ```
240
+
241
+ Conclusions:
242
+
243
+ - **The loop is memory-latency bound, as the roofline diagnosis said.**
244
+ Cutting the corner sum from 24 scalar mul-adds to 8 packed FMAs changes the
245
+ ALU count, not the load latency, so it does nothing. The 8 corner loads are
246
+ independent, so the out-of-order engine already overlaps them (high MLP);
247
+ channel-first's three independent channel loads overlap too.
248
+ - **Channel-first already has good locality.** A 64-byte line holds 16
249
+ consecutive-x voxels of one channel; a trilinear cell's x-pair lives on one
250
+ line. Interleaving with `VS=4` cuts that to 4 voxels/line — *worse* density —
251
+ which is why VS=4 lost ~0.12 s/1T to VS=3. But even VS=3 (no waste, same
252
+ 144 MB footprint) did not beat channel-first.
253
+ - **Padding `VS=4` also costs +33 % flow memory** (144 → 192 MB for the 3D
254
+ fixture) for the AVX2 path's aligned 4-wide load.
255
+ - The differences are partly inside this laptop's **±~8 % thermal-throttle
256
+ noise** (baseline 1T alone ranged 5.33–5.85 s across the session, 8T
257
+ 1.357–1.59 s). That noise floor exceeds the expected gain of any remaining
258
+ micro-optimization, so small wins cannot be validated on this host — they
259
+ need a fixed-clock machine with `perf` counters.
260
+
202
261
  ## What was tried and rejected
203
262
 
204
263
  - **Per-thread density scatter buffers** — scatter is <1 % of runtime,
@@ -212,25 +271,36 @@ plus a careful microbenchmark of `vpgatherdd` cost on the target CPUs.
212
271
  - **Half-precision (fp16) flow storage** — would halve memory traffic, but
213
272
  the kernel isn't bandwidth-bound (see diagnosis), and it's a breaking API
214
273
  change.
274
+ - **Interleaved channel-last layout + hand-written AVX2 FMA** — implemented and
275
+ measured 2026-06-12 (see the dedicated section above). No win, small
276
+ regression, rolled back. The SIMD path was confirmed optimal in codegen yet
277
+ did not beat scalar — the loop is load-latency bound, not ALU bound.
215
278
 
216
279
  ## What remains worth trying
217
280
 
218
- In rough order of expected return on effort:
219
-
220
- 1. **Hand-written AVX2 (and SSE2-fallback) intrinsics for the corner sum**
221
- inside a runtime-dispatched specialization. Plausible 20–30 % at 1T. The
222
- plumbing scaffolding is documented above in the SIMD section.
223
- 2. **SoA position layout** (`vector<float> pos_x, pos_y, pos_z` instead of
224
- `vector<array<float,3>>`). Lets the autovectorizer batch the clip + step
225
- + convergence check across N particles. Pairs naturally with (1).
226
- 3. **Reduce gather miss latency** via batch prefetching: prefetch the
227
- next-K particles' corner cache lines before computing the current one.
228
- Likely modest gain; needs profiling.
281
+ In rough order of expected return on effort. **Note (2026-06-12):** items 1 and
282
+ 2 below were implemented and measured — they gave no speedup (see "Interleaved
283
+ layout + AVX2 FMA" above). They are kept here only with that caveat; the live
284
+ candidate is now (3), and (4) is the prerequisite for evaluating it.
285
+
286
+ 1. ~~Hand-written AVX2 intrinsics for the corner sum~~ — **tried, no win.** The
287
+ corner sum on an interleaved buffer compiled to 8 packed FMAs with no
288
+ gathers, and was no faster than scalar: the loop waits on the 8 corner
289
+ loads, not the arithmetic.
290
+ 2. ~~SoA position layout~~ its only rationale was enabling the SIMD in (1);
291
+ with (1) shown not to help, SoA's batched clip/step/convergence has nothing
292
+ to pay for the extra coordinate gather/scatter. Not pursued separately.
293
+ 3. **Reduce load latency** via software prefetching: prefetch the next-K
294
+ particles' corner cache lines before computing the current one. This is the
295
+ one lever that targets the actual (latency) bottleneck, and it works on the
296
+ existing channel-first layout — no relayout needed. Expected gain is modest
297
+ and **cannot be validated on the current laptop** (its ±~8 % thermal noise
298
+ floor swamps it); needs a fixed-clock host.
229
299
  4. **A real `perf stat`** run on a host where `linux-perf-tools` is
230
300
  installed, to confirm where cycles actually go (front-end, back-end
231
- memory, back-end core, retired-FMA-ratio). The streaming-probe upper
232
- bound is necessary but not sufficient counter data would localise the
233
- bottleneck precisely.
301
+ memory, back-end core, retired-FMA-ratio) and to provide a low-noise
302
+ measurement environment in which (3) could actually be evaluated. The
303
+ streaming-probe upper bound is necessary but not sufficient.
234
304
 
235
305
  ## Reproducing these measurements
236
306
 
@@ -0,0 +1,82 @@
1
+ """Benchmark RAG label accumulation (``accumulate_labels``).
2
+
3
+ The code review replaced the per-thread ``n_threads x n_nodes`` map-of-maps with
4
+ a single combined-key ``(node, other)`` histogram per thread, and made the
5
+ per-node argmax single-threaded. This script times ``accumulate_labels`` across
6
+ thread counts (to confirm the allocation win and rule out a high-thread-count
7
+ regression from the now-sequential argmax) and across ``other``-label
8
+ cardinalities.
9
+
10
+ Inputs: a deterministic block-wise over-segmentation (each node is a contiguous
11
+ block, as in a real over-segmentation), so the RAG adjacency is local.
12
+
13
+ Not part of the test suite. Run::
14
+
15
+ python development/graph/benchmark_accumulate_labels.py --repeats 11
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ from statistics import median
21
+ from time import perf_counter
22
+
23
+ import numpy as np
24
+
25
+ import bioimage_cpp as bic
26
+
27
+
28
+ def _timeit(fn, repeats: int, warmup: int = 1) -> dict:
29
+ for _ in range(warmup):
30
+ fn()
31
+ timings = []
32
+ for _ in range(repeats):
33
+ t0 = perf_counter()
34
+ fn()
35
+ timings.append(perf_counter() - t0)
36
+ return {"median": median(timings), "min": min(timings), "n": repeats}
37
+
38
+
39
+ def _make_labels(shape=(40, 256, 256), coarsen=(2, 4, 4)) -> np.ndarray:
40
+ coarse_shape = tuple(s // c for s, c in zip(shape, coarsen))
41
+ n_nodes = int(np.prod(coarse_shape))
42
+ coarse = np.arange(n_nodes, dtype=np.uint64).reshape(coarse_shape)
43
+ block = np.ones(coarsen, dtype=np.uint64)
44
+ return np.ascontiguousarray(np.kron(coarse, block))
45
+
46
+
47
+ def run(repeats: int = 11) -> dict:
48
+ labels = _make_labels()
49
+ rag = bic.graph.region_adjacency_graph(labels)
50
+ n_nodes = int(rag.number_of_nodes)
51
+ rng = np.random.default_rng(0)
52
+
53
+ results: dict[str, dict] = {}
54
+ for n_other, tag in ((8, "dense8"), (2000, "sparse2000")):
55
+ other = rng.integers(0, n_other, size=labels.shape).astype(np.uint64)
56
+ other = np.ascontiguousarray(other)
57
+ for threads in (1, 4, 8):
58
+ def fn(t=threads, o=other):
59
+ bic.graph.features.accumulate_labels(rag, labels, o, number_of_threads=t)
60
+
61
+ key = f"accumulate_labels_{tag}_t{threads}"
62
+ results[key] = _timeit(fn, repeats)
63
+ results[key]["meta"] = {
64
+ "n_nodes": n_nodes,
65
+ "n_pixels": int(labels.size),
66
+ "n_other": n_other,
67
+ "threads": threads,
68
+ }
69
+ return results
70
+
71
+
72
+ def main() -> None:
73
+ parser = argparse.ArgumentParser(description=__doc__)
74
+ parser.add_argument("--repeats", type=int, default=11)
75
+ args = parser.parse_args()
76
+ results = run(repeats=args.repeats)
77
+ for name, r in results.items():
78
+ print(f"{name:<32} median={r['median'] * 1e3:8.3f} ms min={r['min'] * 1e3:8.3f} ms")
79
+
80
+
81
+ if __name__ == "__main__":
82
+ main()
@@ -0,0 +1,70 @@
1
+ """Benchmark the workspace-reused BFS path (``lifted_edges_from_node_labels``).
2
+
3
+ The code review changed ``BfsWorkspace::reset`` from an O(N) clear of the
4
+ visited/distance buffers to an O(1) generation-stamp bump. That only pays off
5
+ when one workspace is reset across many sources, which is exactly what
6
+ ``lifted_edges_from_node_labels`` does (one workspace per chunk, reset per
7
+ source). The single-call ``breadth_first_search`` builds a fresh workspace each
8
+ call and would NOT show the change, so we benchmark the lifted-edge path.
9
+
10
+ Single-threaded so the per-source reset cost is not hidden by parallelism. The
11
+ node count is swept to expose the previous O(N^2)-of-memset behavior.
12
+
13
+ Not part of the test suite. Run::
14
+
15
+ python development/graph/benchmark_bfs.py --repeats 5
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ from statistics import median
21
+ from time import perf_counter
22
+
23
+ import numpy as np
24
+
25
+ import bioimage_cpp as bic
26
+
27
+
28
+ def _timeit(fn, repeats: int, warmup: int = 1) -> dict:
29
+ for _ in range(warmup):
30
+ fn()
31
+ timings = []
32
+ for _ in range(repeats):
33
+ t0 = perf_counter()
34
+ fn()
35
+ timings.append(perf_counter() - t0)
36
+ return {"median": median(timings), "min": min(timings), "n": repeats}
37
+
38
+
39
+ def run(repeats: int = 5, depth: int = 2) -> dict:
40
+ shapes = [(100, 100), (160, 160), (220, 220)]
41
+ results: dict[str, dict] = {}
42
+ for shape in shapes:
43
+ n_nodes = int(np.prod(shape))
44
+ graph = bic.graph.grid_graph(shape)
45
+ rng = np.random.default_rng(0)
46
+ node_labels = rng.integers(0, 50, size=(n_nodes,), dtype=np.uint64)
47
+
48
+ def fn(g=graph, nl=node_labels, d=depth):
49
+ bic.graph.lifted_multicut.lifted_edges_from_node_labels(
50
+ g, nl, graph_depth=d, number_of_threads=1
51
+ )
52
+
53
+ key = f"bfs_lifted_{n_nodes}nodes_d{depth}"
54
+ results[key] = _timeit(fn, repeats)
55
+ results[key]["meta"] = {"n_nodes": n_nodes, "shape": shape, "depth": depth}
56
+ return results
57
+
58
+
59
+ def main() -> None:
60
+ parser = argparse.ArgumentParser(description=__doc__)
61
+ parser.add_argument("--repeats", type=int, default=5)
62
+ parser.add_argument("--depth", type=int, default=2)
63
+ args = parser.parse_args()
64
+ results = run(repeats=args.repeats, depth=args.depth)
65
+ for name, r in results.items():
66
+ print(f"{name:<28} median={r['median'] * 1e3:9.3f} ms min={r['min'] * 1e3:9.3f} ms")
67
+
68
+
69
+ if __name__ == "__main__":
70
+ main()
@@ -0,0 +1,89 @@
1
+ """Benchmark the mutex-watershed grid kernel (bic only).
2
+
3
+ The code review routed the neighbor computation in ``mutex_watershed_grid``
4
+ through ``detail::valid_offset_target`` (per-axis bounds check) instead of a
5
+ single precomputed flat-offset add. This script times ``mutex_watershed`` on the
6
+ ISBI affinities for 2D and 3D so we can A/B that inner-loop change against a
7
+ pre-review build. No external (affogato) dependency — bic only.
8
+
9
+ Not part of the test suite. Run::
10
+
11
+ python development/segmentation/benchmark_mutex_watershed.py --repeats 5
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ from statistics import median
17
+ from time import perf_counter
18
+
19
+ import numpy as np
20
+
21
+ import bioimage_cpp as bic
22
+ from bioimage_cpp._data import load_isbi_affinities
23
+
24
+
25
+ def _timeit(fn, repeats: int, warmup: int = 1) -> dict:
26
+ for _ in range(warmup):
27
+ fn()
28
+ timings = []
29
+ for _ in range(repeats):
30
+ t0 = perf_counter()
31
+ fn()
32
+ timings.append(perf_counter() - t0)
33
+ return {"median": median(timings), "min": min(timings), "n": repeats}
34
+
35
+
36
+ def _attractive_flip(affs: np.ndarray, n_attractive: int) -> np.ndarray:
37
+ # Match the convention in the equivalence checker: attractive channels are
38
+ # turned into merge affinities (1 - aff).
39
+ out = affs.copy()
40
+ out[:n_attractive] *= -1
41
+ out[:n_attractive] += 1
42
+ return out
43
+
44
+
45
+ def run(repeats: int = 5) -> dict:
46
+ affinities, offsets = load_isbi_affinities()
47
+ affinities = np.ascontiguousarray(affinities)
48
+ offsets = [tuple(o) for o in offsets]
49
+ results: dict[str, dict] = {}
50
+
51
+ # --- 2D: in-plane channels of a single z slice ---
52
+ channels_2d = [i for i, o in enumerate(offsets) if o[0] == 0]
53
+ aff2d = np.ascontiguousarray(affinities[channels_2d, 0, :256, :256])
54
+ offsets_2d = [offsets[i][1:] for i in channels_2d]
55
+ aff2d_flipped = _attractive_flip(aff2d, 2)
56
+
57
+ def mws_2d():
58
+ bic.segmentation.mutex_watershed(
59
+ aff2d_flipped, offsets_2d, number_of_attractive_channels=2
60
+ )
61
+
62
+ results["mws_2d_256x256"] = _timeit(mws_2d, repeats)
63
+ results["mws_2d_256x256"]["meta"] = {"shape": list(aff2d.shape)}
64
+
65
+ # --- 3D: small crop, all offsets ---
66
+ aff3d = np.ascontiguousarray(affinities[:, :6, :256, :256])
67
+ aff3d_flipped = _attractive_flip(aff3d, 3)
68
+
69
+ def mws_3d():
70
+ bic.segmentation.mutex_watershed(
71
+ aff3d_flipped, offsets, number_of_attractive_channels=3
72
+ )
73
+
74
+ results["mws_3d_6x256x256"] = _timeit(mws_3d, repeats)
75
+ results["mws_3d_6x256x256"]["meta"] = {"shape": list(aff3d.shape)}
76
+ return results
77
+
78
+
79
+ def main() -> None:
80
+ parser = argparse.ArgumentParser(description=__doc__)
81
+ parser.add_argument("--repeats", type=int, default=5)
82
+ args = parser.parse_args()
83
+ results = run(repeats=args.repeats)
84
+ for name, r in results.items():
85
+ print(f"{name:<20} median={r['median'] * 1e3:9.3f} ms min={r['min'] * 1e3:9.3f} ms")
86
+
87
+
88
+ if __name__ == "__main__":
89
+ main()
@@ -0,0 +1,70 @@
1
+ """Micro-benchmark for the UnionFind Python bindings.
2
+
3
+ Times the bulk ``merge((N, 2) edges)`` and ``find(node_array)`` entry points,
4
+ which the code review changed to validate every node id against ``uf.size``
5
+ before touching the C++ structure (an extra O(N) pre-pass). This script lets us
6
+ A/B that pre-pass against a pre-review build.
7
+
8
+ Not part of the test suite. Run::
9
+
10
+ python development/utils/benchmark_union_find.py --repeats 11
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ from statistics import median
16
+ from time import perf_counter
17
+
18
+ import numpy as np
19
+
20
+ import bioimage_cpp as bic
21
+
22
+
23
+ def _timeit(fn, repeats: int, warmup: int = 1) -> dict:
24
+ for _ in range(warmup):
25
+ fn()
26
+ timings = []
27
+ for _ in range(repeats):
28
+ t0 = perf_counter()
29
+ fn()
30
+ timings.append(perf_counter() - t0)
31
+ return {"median": median(timings), "min": min(timings), "n": repeats}
32
+
33
+
34
+ def run(repeats: int = 11, n_nodes: int = 1_000_000, n_edges: int = 2_000_000) -> dict:
35
+ rng = np.random.default_rng(0)
36
+ edges = rng.integers(0, n_nodes, size=(n_edges, 2), dtype=np.uint64)
37
+ query = rng.integers(0, n_nodes, size=(n_edges,), dtype=np.uint64)
38
+
39
+ # Pre-merged structure for the find benchmark (find does not mutate the
40
+ # partition, so it can be reused across repeats).
41
+ merged = bic.utils.UnionFind(n_nodes)
42
+ merged.merge(edges)
43
+
44
+ def bulk_merge():
45
+ uf = bic.utils.UnionFind(n_nodes)
46
+ uf.merge(edges)
47
+
48
+ def bulk_find():
49
+ merged.find(query)
50
+
51
+ results = {
52
+ "uf_bulk_merge": _timeit(bulk_merge, repeats),
53
+ "uf_bulk_find": _timeit(bulk_find, repeats),
54
+ }
55
+ for r in results.values():
56
+ r["meta"] = {"n_nodes": n_nodes, "n_edges": n_edges}
57
+ return results
58
+
59
+
60
+ def main() -> None:
61
+ parser = argparse.ArgumentParser(description=__doc__)
62
+ parser.add_argument("--repeats", type=int, default=11)
63
+ args = parser.parse_args()
64
+ results = run(repeats=args.repeats)
65
+ for name, r in results.items():
66
+ print(f"{name:<16} median={r['median'] * 1e3:8.3f} ms min={r['min'] * 1e3:8.3f} ms")
67
+
68
+
69
+ if __name__ == "__main__":
70
+ main()
@@ -6,6 +6,16 @@
6
6
 
7
7
  namespace bioimage_cpp::detail {
8
8
 
9
+ // Total number of elements in a row-major array of the given shape (the product
10
+ // of the per-axis extents). Shape entries are assumed non-negative.
11
+ inline std::size_t number_of_elements(const std::vector<std::ptrdiff_t> &shape) {
12
+ std::size_t total = 1;
13
+ for (const auto extent : shape) {
14
+ total *= static_cast<std::size_t>(extent);
15
+ }
16
+ return total;
17
+ }
18
+
9
19
  // C-order strides for a row-major array of the given shape, in units of array
10
20
  // elements (not bytes). The innermost (last) axis has stride 1.
11
21
  inline std::vector<std::ptrdiff_t> c_order_strides(const std::vector<std::ptrdiff_t> &shape) {
@@ -89,7 +89,12 @@ inline ProfileTimerNull make_profile_timer(NullProfiler &profiler, const char *n
89
89
  } // namespace bioimage_cpp::detail
90
90
 
91
91
  #define BIOIMAGE_PROFILE_INIT(var) ::bioimage_cpp::detail::Profiler var;
92
- #define BIOIMAGE_PROFILE_SCOPE(var, name) auto _bp_##__LINE__ = ::bioimage_cpp::detail::make_profile_timer(var, name);
92
+ // Two-level indirection so __LINE__ is expanded before the token paste; without
93
+ // it every BIOIMAGE_PROFILE_SCOPE in a translation unit would declare the same
94
+ // identifier `_bp___LINE__`, breaking two scopes in one block.
95
+ #define BIOIMAGE_PROFILE_CONCAT_(a, b) a##b
96
+ #define BIOIMAGE_PROFILE_CONCAT(a, b) BIOIMAGE_PROFILE_CONCAT_(a, b)
97
+ #define BIOIMAGE_PROFILE_SCOPE(var, name) auto BIOIMAGE_PROFILE_CONCAT(_bp_, __LINE__) = ::bioimage_cpp::detail::make_profile_timer(var, name);
93
98
  #define BIOIMAGE_PROFILE_REPORT(var) (var).report();
94
99
 
95
100
  #else
@@ -109,7 +109,10 @@ inline std::ptrdiff_t round_to_flat_index(
109
109
  } else if (clipped > grid.upper[axis]) {
110
110
  clipped = grid.upper[axis];
111
111
  }
112
- const auto coord = static_cast<std::ptrdiff_t>(std::nearbyint(clipped));
112
+ // Round half up, matching the nearest-neighbor convention in
113
+ // transformation/affine.hxx and segmentation/watershed.hxx.
114
+ // std::nearbyint would honor the FP rounding mode (round-half-to-even).
115
+ const auto coord = static_cast<std::ptrdiff_t>(std::floor(clipped + 0.5f));
113
116
  flat += coord * grid.strides[axis];
114
117
  }
115
118
  return flat;
@@ -93,8 +93,9 @@ inline std::size_t agglo_merge_dynamic_nodes(
93
93
  if (new_priority != current_priority) {
94
94
  edge.weight = new_priority;
95
95
  // The edge may have been previously popped via kRejectEdge
96
- // (GASP cannot-link), in which case it is no longer in the
97
- // heap. Use push_or_change to handle both cases.
96
+ // (GASP cannot-link). In that case it must stay out of the heap
97
+ // (re-adding it would let it be re-popped and merged), so only
98
+ // update the priority when the edge is still present.
98
99
  if (heap.contains(removed_edge_id)) {
99
100
  heap.change(removed_edge_id, new_priority);
100
101
  }