@@ -70,6 +70,12 @@ def select_mixed_offsets(
7070
7171
7272def time_call (function : Callable [[], tuple [np .ndarray , np .ndarray ]], repeats : int ):
73+ # One untimed warm-up before the measured loop. The first call typically
74+ # pays for code-page faults and one-time library initialization
75+ # (nanobind tuple shapes, numpy ufunc caches, ...) that aren't part of
76+ # the steady-state cost we care about. Without warm-up these costs leak
77+ # into the first sample and skew the median for low `repeats` values.
78+ function ()
7379 timings = []
7480 result = None
7581 for _ in range (repeats ):
@@ -104,34 +110,78 @@ def bioimage_cpp_local(affinities: np.ndarray, offsets: list[tuple[int, ...]]):
104110 import bioimage_cpp as bic
105111
106112 graph = bic .graph .grid_graph (affinities .shape [1 :])
107- return bioimage_cpp_local_on_graph (graph , affinities , offsets )
113+ weights , _ = bic .graph .grid_affinity_features (graph , affinities , offsets )
114+ return graph .uv_ids (), weights
115+
108116
117+ def bioimage_cpp_local_weights_only (
118+ graph , affinities : np .ndarray , offsets : list [tuple [int , ...]]
119+ ):
120+ """Compute edge weights only — no (uvs, weights) materialization.
109121
110- def bioimage_cpp_local_on_graph (graph , affinities : np .ndarray , offsets : list [tuple [int , ...]]):
122+ This isolates the cost of the feature kernel from the cost of returning
123+ the canonical uv_ids array. Use this when comparing against libraries
124+ that already cache uvs in the graph object.
125+ """
111126 import bioimage_cpp as bic
112127
113- weights , valid_edges = bic .graph .grid_affinity_features (graph , affinities , offsets )
114- if not np .all (valid_edges ):
115- raise AssertionError ("local offsets did not cover all grid edges" )
128+ weights , _ = bic .graph .grid_affinity_features (graph , affinities , offsets )
129+ return weights
130+
131+
132+ def bioimage_cpp_local_with_uvs (
133+ graph , affinities : np .ndarray , offsets : list [tuple [int , ...]]
134+ ):
135+ """Compute weights AND materialize uvs — apples-to-apples with nifty's
136+ ``affinitiesToEdgeMapWithOffsets`` and affogato's
137+ ``compute_nh_and_weights``, both of which return uvs in their output."""
138+ import bioimage_cpp as bic
139+
140+ weights , _ = bic .graph .grid_affinity_features (graph , affinities , offsets )
116141 return graph .uv_ids (), weights
117142
118143
119144def bioimage_cpp_lifted (affinities : np .ndarray , offsets : list [tuple [int , ...]]):
120145 import bioimage_cpp as bic
121146
122147 graph = bic .graph .grid_graph (affinities .shape [1 :])
123- return bioimage_cpp_lifted_on_graph (graph , affinities , offsets )
148+ local_weights , _ , lifted_uvs , lifted_weights , _ = (
149+ bic .graph .grid_affinity_features_with_lifted (graph , affinities , offsets )
150+ )
151+ return graph , graph .uv_ids (), local_weights , lifted_uvs , lifted_weights
124152
125153
126- def bioimage_cpp_lifted_on_graph (graph , affinities : np .ndarray , offsets : list [tuple [int , ...]]):
154+ def bioimage_cpp_lifted_features_only (
155+ graph , affinities : np .ndarray , offsets : list [tuple [int , ...]]
156+ ):
157+ """Lifted features without graph.uv_ids() — see the local variant."""
127158 import bioimage_cpp as bic
128159
129- local_weights , valid_edges , lifted_uvs , lifted_weights , _ = (
160+ local_weights , _ , lifted_uvs , lifted_weights , _ = (
130161 bic .graph .grid_affinity_features_with_lifted (graph , affinities , offsets )
131162 )
163+ return local_weights , lifted_uvs , lifted_weights
164+
165+
166+ def bioimage_cpp_lifted_with_uvs (
167+ graph , affinities : np .ndarray , offsets : list [tuple [int , ...]]
168+ ):
169+ """Lifted features WITH local uvs (apples-to-apples with affogato)."""
170+ import bioimage_cpp as bic
171+
172+ local_weights , _ , lifted_uvs , lifted_weights , _ = (
173+ bic .graph .grid_affinity_features_with_lifted (graph , affinities , offsets )
174+ )
175+ return graph .uv_ids (), local_weights , lifted_uvs , lifted_weights
176+
177+
178+ def assert_local_offsets_cover_all_edges (graph , affinities , offsets ) -> None :
179+ """One-shot correctness check called outside of the timing loop."""
180+ import bioimage_cpp as bic
181+
182+ _ , valid_edges = bic .graph .grid_affinity_features (graph , affinities , offsets )
132183 if not np .all (valid_edges ):
133184 raise AssertionError ("local offsets did not cover all grid edges" )
134- return graph , graph .uv_ids (), local_weights , lifted_uvs , lifted_weights
135185
136186
137187def nifty_local (affinities : np .ndarray , offsets : list [tuple [int , ...]]):
@@ -206,7 +256,19 @@ def print_timing(name: str, first_name: str, first_timings: list[float],
206256def add_common_arguments (parser : argparse .ArgumentParser ) -> None :
207257 parser .add_argument ("--ndim" , type = int , choices = (2 , 3 ), default = 2 )
208258 parser .add_argument ("--data-prefix" , type = Path , default = DEFAULT_DATA_PREFIX )
209- parser .add_argument ("--repeats" , type = int , default = 3 )
259+ # Default bumped from 3 to 5 — median of 3 is the middle sample and is
260+ # noisy if anything (GC, cache eviction) lands inside one of the three
261+ # runs. With `time_call` doing one warm-up before this, 5 samples gives
262+ # a usable median without much added cost.
263+ parser .add_argument ("--repeats" , type = int , default = 5 )
210264 parser .add_argument ("--z" , type = int , default = 20 )
211265 parser .add_argument ("--yx-shape" , type = int , nargs = 2 , default = (512 , 512 ))
212266 parser .add_argument ("--zyx-shape" , type = int , nargs = 3 , default = (16 , 512 , 512 ))
267+ # Affinity dtype that every library receives. nifty and affogato accept
268+ # both float32 and float64 at near-identical speed (verified separately),
269+ # and bioimage-cpp now templates on the value type, so feeding all three
270+ # the same dtype removes the previous implicit float32 -> float64 copy
271+ # that was charged only to bioimage-cpp.
272+ parser .add_argument (
273+ "--dtype" , choices = ("float32" , "float64" ), default = "float32"
274+ )
0 commit comments