bioimage-cpp 0.3.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.
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/AGENTS.md +2 -2
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/PKG-INFO +11 -1
- bioimage_cpp-0.4.0/README.md +20 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/flow/PERFORMANCE_NOTES.md +84 -14
- bioimage_cpp-0.4.0/development/graph/benchmark_accumulate_labels.py +82 -0
- bioimage_cpp-0.4.0/development/graph/benchmark_bfs.py +70 -0
- bioimage_cpp-0.4.0/development/segmentation/benchmark_mutex_watershed.py +89 -0
- bioimage_cpp-0.4.0/development/utils/benchmark_union_find.py +70 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/grid.hxx +10 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/profile.hxx +6 -1
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/flow/flow_density.hxx +4 -1
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/detail.hxx +3 -2
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/breadth_first_search.hxx +30 -9
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/feature_accumulation.hxx +5 -9
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/label_accumulation.hxx +6 -12
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/greedy_additive.hxx +3 -1
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/greedy_additive.hxx +3 -1
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/node_label_projection.hxx +2 -11
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/proposal_generators/greedy_additive_multicut.hxx +18 -1
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/proposal_generators/watershed.hxx +3 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/label_multiset/multiset.hxx +12 -2
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/non_maximum_distance_suppression.hxx +3 -1
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/segmentation/mutex_watershed.hxx +5 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/segmentation/semantic_mutex_watershed.hxx +4 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/filters.cxx +16 -16
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/segmentation.cxx +1 -5
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/util.cxx +53 -4
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/__init__.py +19 -3
- bioimage_cpp-0.4.0/src/bioimage_cpp/_version.py +1 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/distance/test_non_maximum_distance_suppression.py +14 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/label_multiset/test_downsample.py +18 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/test_util_union_find.py +30 -0
- bioimage_cpp-0.3.0/README.md +0 -10
- bioimage_cpp-0.3.0/src/bioimage_cpp/_version.py +0 -1
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/.github/workflows/docs.yml +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/.github/workflows/tests.yml +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/.github/workflows/wheels.yml +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/.gitignore +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/CMakeLists.txt +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/LICENSE +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/MIGRATION_GUIDE.md +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/affinities/check_compute_affinities.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/affinities/check_compute_embedding_distances.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/distance/benchmark.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/distance/check_non_maximum_distance_suppression.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/filters/PERFORMANCE_NOTES.md +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/filters/_bench_utils.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/filters/benchmark.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/filters/check_parity.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/flow/_reference_impl.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/flow/benchmark.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/flow/check_flow_density.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/flow/create_test_data.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/_grid_affinity_compatibility.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/_rag_compatibility.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/PERFORMANCE_NOTES.md +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/_compatibility.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/check_edge_weighted.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/check_gasp.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/check_mala.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/check_node_and_edge_weighted.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/agglomeration/diagnose.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/check_grid_affinity_edges.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/check_grid_affinity_lifted_edges.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/check_rag_2d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/check_rag_3d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/PERFORMANCE_NOTES.md +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/_compatibility.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/check_fusion_move.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/check_greedy_additive.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/check_kernighan_lin.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/evaluate_solvers.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/lifted_multicut/repro_lifted_edges_segfault.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/multicut/PERFORMANCE_NOTES.md +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/multicut/_compatibility.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_chained.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_decomposer.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_fusion_move.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_greedy_additive.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_greedy_fixation.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/multicut/check_kernighan_lin.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/multicut/evaluate_solvers.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/mutex_watershed/check_mutex_clustering.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/graph/mutex_watershed/check_semantic_mutex_clustering.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/label_multiset/benchmark.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/local_maxima/benchmark.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/PERFORMANCE_NOTES.md +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/_label_equivalence.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/_mutex_watershed_equivalence.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/_semantic_mws_equivalence.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/_watershed_equivalence.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/_watershed_from_affinities_equivalence.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_label_2d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_label_3d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_mutex_watershed_2d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_mutex_watershed_3d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_relabel_sequential.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_semantic_mutex_watershed_2d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_semantic_mutex_watershed_3d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_watershed_2d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_watershed_3d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_watershed_from_affinities_2d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/segmentation/check_watershed_from_affinities_3d.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/transformation/PERFORMANCE_NOTES.md +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/transformation/check_affine.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/utils/_mesh_smoothing_reference.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/development/utils/benchmark_mesh_smoothing.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/examples/segmentation/_lifted_problem.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/examples/segmentation/lifted_multicut_from_affinities.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/examples/segmentation/multicut_from_affinities.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/examples/segmentation/mutex_watershed.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/examples/segmentation/serialize_grid_lifted_problem.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/examples/segmentation/serialize_lifted_problem.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/examples/segmentation/watershed.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/affinities/compute_affinities.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/affinities/compute_embedding_distances.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/array_view.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/blocking.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/edge_hash.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/indexed_heap.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/mutex_storage.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/relabel.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/semantic_labels.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/detail/threading.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/distance_transform.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/filters/convolve.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/filters/eigenvalues.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/filters/gaussian.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/filters/kernel.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/agglomerative_clustering.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/cluster_policy_base.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/edge_weighted.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/gasp.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/mala.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/node_and_edge_weighted.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/connected_components.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/detail/fusion_contract.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/edge_weighted_watershed.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/grid_edge_projection.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/grid_features.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/grid_graph.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_from_affinities.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/detail.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/fusion_move.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/kernighan_lin.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/lifted_from_node_labels.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut/objective.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/lifted_multicut.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/detail.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/fusion_move.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/greedy_fixation.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/kernighan_lin.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut/objective.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/multicut.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/mutex_watershed/clustering.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/mutex_watershed/semantic.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/mutex_watershed.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/proposal_generator.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/rag_coordinates.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/region_adjacency_graph.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/undirected_graph.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/label_multiset/downsample.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/label_multiset/from_labels.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/label_multiset/merger.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/label_multiset/read_subset.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/mesh_smoothing.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/overlap.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/run_length.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/segmentation/connected_components.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/segmentation/relabel_sequential.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/segmentation/watershed.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/take_dict.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/transformation/affine.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/util/union_find.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/pyproject.toml +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/affinities.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/affinities.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/blocking.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/blocking.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/distance.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/distance.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/filters.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/flow.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/flow.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/graph.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/graph.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/ground_truth.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/ground_truth.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/label_multiset.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/label_multiset.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/module.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/segmentation.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/transformation.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/transformation.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/util.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/utils.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bindings/utils.hxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/_data.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/affinities/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/affinities/compute_affinities.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/affinities/compute_embedding_distances.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/distance/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/distance/_distance.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/filters/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/filters/_filters.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/flow/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/flow/_flow.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/_external.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/_shared.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/agglomeration.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/features/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/lifted_multicut/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/multicut/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/graph/mutex_watershed.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/label_multiset/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/segmentation/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/segmentation/label.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/segmentation/mutex_watershed.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/segmentation/relabel_sequential.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/segmentation/watershed.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/transformation/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/transformation/_transformation.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/bioimage_cpp/utils.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/cpp/segmentation/mutex_watershed.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/cpp/segmentation/semantic_mutex_watershed.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/src/cpp/take_dict.cxx +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/affinities/test_compute_affinities.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/affinities/test_compute_embedding_distances.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/distance/test_distance_transform.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/_helpers.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/test_edge_weighted.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/test_gasp.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/test_mala.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/agglomeration/test_node_and_edge_weighted.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/_helpers.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/conftest.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_external_problem.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_fusion_move.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_greedy_additive.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_kernighan_lin.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_lifted_edges_from_node_labels.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/lifted_multicut/test_objective.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/_helpers.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/conftest.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_chain_decomposer.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_external_problem.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_fusion_move.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_greedy_additive.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_greedy_fixation.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_kernighan_lin.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_objective.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/multicut/test_proposal_generators.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_breadth_first_search.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_connected_components.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_edge_weighted_watershed.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_grid_affinity_multicut_integration.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_grid_features.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_grid_graph.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_mutex_watershed_graph.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_node_label_projection.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_rag_accumulate_labels.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_rag_coordinates.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_rag_features.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_rag_lifted_features.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_rag_multicut_integration.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_region_adjacency_graph.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_semantic_mutex_watershed_graph.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/graph/test_undirected_graph.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/label_multiset/__init__.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/label_multiset/test_against_nifty.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/label_multiset/test_merger.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/label_multiset/test_read_subset.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_label.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_mutex_watershed.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_relabel_sequential.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_semantic_mutex_watershed.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_watershed.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/segmentation/test_watershed_from_affinities.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/test_blocking.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/test_filters.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/test_flow.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/test_ground_truth_overlap.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/test_transformation.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/test_utils_compute_rle.py +0 -0
- {bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/tests/test_utils_mesh_smoothing.py +0 -0
- {bioimage_cpp-0.3.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
|
-
- `
|
|
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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: bioimage-cpp
|
|
3
|
-
Version: 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
|
+
[](https://computational-cell-analytics.github.io/bioimage-cpp/)
|
|
35
|
+
[](https://github.com/computational-cell-analytics/bioimage-cpp/actions/workflows/tests.yml)
|
|
36
|
+
[](https://pypi.org/project/bioimage-cpp/)
|
|
37
|
+
[](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
|
+
[](https://computational-cell-analytics.github.io/bioimage-cpp/)
|
|
4
|
+
[](https://github.com/computational-cell-analytics/bioimage-cpp/actions/workflows/tests.yml)
|
|
5
|
+
[](https://pypi.org/project/bioimage-cpp/)
|
|
6
|
+
[](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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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)
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
{bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/agglomeration/detail.hxx
RENAMED
|
@@ -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)
|
|
97
|
-
//
|
|
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
|
}
|
{bioimage_cpp-0.3.0 → bioimage_cpp-0.4.0}/include/bioimage_cpp/graph/breadth_first_search.hxx
RENAMED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include "bioimage_cpp/graph/undirected_graph.hxx"
|
|
4
4
|
|
|
5
|
+
#include <algorithm>
|
|
5
6
|
#include <cstddef>
|
|
6
7
|
#include <cstdint>
|
|
7
8
|
#include <limits>
|
|
@@ -19,23 +20,44 @@ struct BfsEntry {
|
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
// Reusable scratch state for `breadth_first_search`. Reset via `reset(graph)`
|
|
22
|
-
// before each call so the visited
|
|
23
|
+
// before each call so the visited and distance buffers grow once and stay
|
|
23
24
|
// allocated across many BFS invocations on the same graph.
|
|
25
|
+
//
|
|
26
|
+
// Visited tracking uses a per-call generation stamp rather than a boolean
|
|
27
|
+
// buffer cleared each call: `reset` just increments the generation (O(1)),
|
|
28
|
+
// doing a full clear only on the rare 32-bit wraparound. This matters for the
|
|
29
|
+
// "k-hop neighborhood from every node" pattern, where an O(N)-per-call clear
|
|
30
|
+
// would otherwise make the whole sweep O(N^2). `distance_` is sized but not
|
|
31
|
+
// cleared; it is written before being read for every visited node.
|
|
24
32
|
class BfsWorkspace {
|
|
25
33
|
public:
|
|
26
34
|
BfsWorkspace() = default;
|
|
27
35
|
|
|
28
36
|
void reset(const UndirectedGraph &graph) {
|
|
29
37
|
const auto n_nodes = static_cast<std::size_t>(graph.number_of_nodes());
|
|
30
|
-
visited_.
|
|
31
|
-
|
|
38
|
+
if (visited_.size() != n_nodes) {
|
|
39
|
+
visited_.assign(n_nodes, 0);
|
|
40
|
+
generation_ = 0;
|
|
41
|
+
}
|
|
42
|
+
distance_.resize(n_nodes);
|
|
43
|
+
if (generation_ == std::numeric_limits<std::uint32_t>::max()) {
|
|
44
|
+
std::fill(visited_.begin(), visited_.end(), std::uint32_t{0});
|
|
45
|
+
generation_ = 0;
|
|
46
|
+
}
|
|
47
|
+
++generation_;
|
|
32
48
|
}
|
|
33
49
|
|
|
34
|
-
[[nodiscard]] std::
|
|
50
|
+
[[nodiscard]] bool is_visited(const std::uint64_t node) const {
|
|
51
|
+
return visited_[static_cast<std::size_t>(node)] == generation_;
|
|
52
|
+
}
|
|
53
|
+
void mark_visited(const std::uint64_t node) {
|
|
54
|
+
visited_[static_cast<std::size_t>(node)] = generation_;
|
|
55
|
+
}
|
|
35
56
|
[[nodiscard]] std::vector<std::uint64_t> &distance() { return distance_; }
|
|
36
57
|
|
|
37
58
|
private:
|
|
38
|
-
std::vector<
|
|
59
|
+
std::vector<std::uint32_t> visited_;
|
|
60
|
+
std::uint32_t generation_ = 0;
|
|
39
61
|
std::vector<std::uint64_t> distance_;
|
|
40
62
|
};
|
|
41
63
|
|
|
@@ -71,13 +93,12 @@ inline std::vector<BfsEntry> breadth_first_search(
|
|
|
71
93
|
);
|
|
72
94
|
}
|
|
73
95
|
workspace.reset(graph);
|
|
74
|
-
auto &visited = workspace.visited();
|
|
75
96
|
auto &distance = workspace.distance();
|
|
76
97
|
|
|
77
98
|
std::vector<BfsEntry> result;
|
|
78
99
|
std::queue<std::uint64_t> queue;
|
|
79
100
|
queue.push(source);
|
|
80
|
-
|
|
101
|
+
workspace.mark_visited(source);
|
|
81
102
|
distance[static_cast<std::size_t>(source)] = 0;
|
|
82
103
|
if (include_source) {
|
|
83
104
|
result.push_back({source, 0});
|
|
@@ -93,10 +114,10 @@ inline std::vector<BfsEntry> breadth_first_search(
|
|
|
93
114
|
const auto next_distance = node_distance + 1;
|
|
94
115
|
for (const auto adjacency : graph.node_adjacency(node)) {
|
|
95
116
|
const auto neighbor = adjacency.node;
|
|
96
|
-
if (
|
|
117
|
+
if (workspace.is_visited(neighbor)) {
|
|
97
118
|
continue;
|
|
98
119
|
}
|
|
99
|
-
|
|
120
|
+
workspace.mark_visited(neighbor);
|
|
100
121
|
distance[static_cast<std::size_t>(neighbor)] = next_distance;
|
|
101
122
|
result.push_back({neighbor, next_distance});
|
|
102
123
|
queue.push(neighbor);
|