Skip to content

Commit f8b4284

Browse files
Share one clipped grid sweep between in-core and distributed scans
The 2D/3D offset-box sweeps in feature_accumulation.hxx and the owned-box sweeps in the distributed block extraction duplicated the same clamp + loop logic. Move a single sweep_clipped_box_{2d,3d} with a per-axis clip box into detail/grid.hxx; the in-core wrappers pass the thread slab with unclipped remaining axes, the distributed dispatch folds the owned box and slab into the clips. Inner loops are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 8ada4e2 commit f8b4284

3 files changed

Lines changed: 129 additions & 161 deletions

File tree

include/bioimage_cpp/detail/grid.hxx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <algorithm>
34
#include <cstddef>
45
#include <cstdint>
56
#include <vector>
@@ -109,4 +110,98 @@ inline void valid_axis_range(
109110
}
110111
}
111112

113+
// Sweep every (node, target) pair on a 2D row-major grid for which
114+
// `node + offset` stays inside the grid and the reference node lies in the
115+
// half-open clip box `[clip_y_lo, clip_y_hi) x [clip_x_lo, clip_x_hi)`. The
116+
// body receives flat C-order indices for both endpoints and is expected to
117+
// inline at -O2 since this is a header-only template with a fully-known
118+
// callable type at instantiation. Callers fold any additional restriction —
119+
// a per-thread axis-0 slab, an owned block box — into the clip box; passing
120+
// the full grid extent sweeps every valid reference node. Shared by the
121+
// in-core feature accumulation / lifted-edge sweeps and the distributed
122+
// block extraction.
123+
template <class Body>
124+
void sweep_clipped_box_2d(
125+
const std::ptrdiff_t dy,
126+
const std::ptrdiff_t dx,
127+
const std::size_t height,
128+
const std::size_t width,
129+
const std::size_t clip_y_lo,
130+
const std::size_t clip_y_hi,
131+
const std::size_t clip_x_lo,
132+
const std::size_t clip_x_hi,
133+
const Body &body
134+
) {
135+
std::size_t y_lo_v, y_hi_v, x_lo_v, x_hi_v;
136+
valid_axis_range(dy, height, y_lo_v, y_hi_v);
137+
valid_axis_range(dx, width, x_lo_v, x_hi_v);
138+
const auto y_lo = std::max(y_lo_v, clip_y_lo);
139+
const auto y_hi = std::min(y_hi_v, clip_y_hi);
140+
const auto x_lo = std::max(x_lo_v, clip_x_lo);
141+
const auto x_hi = std::min(x_hi_v, clip_x_hi);
142+
if (y_lo >= y_hi || x_lo >= x_hi) {
143+
return;
144+
}
145+
const auto offset_stride = dy * static_cast<std::ptrdiff_t>(width) + dx;
146+
for (std::size_t y = y_lo; y < y_hi; ++y) {
147+
const auto row_offset = y * width;
148+
for (std::size_t x = x_lo; x < x_hi; ++x) {
149+
const auto node = row_offset + x;
150+
const auto target = static_cast<std::uint64_t>(
151+
static_cast<std::ptrdiff_t>(node) + offset_stride
152+
);
153+
body(static_cast<std::uint64_t>(node), target);
154+
}
155+
}
156+
}
157+
158+
// 3D variant of `sweep_clipped_box_2d`.
159+
template <class Body>
160+
void sweep_clipped_box_3d(
161+
const std::ptrdiff_t dz,
162+
const std::ptrdiff_t dy,
163+
const std::ptrdiff_t dx,
164+
const std::size_t depth,
165+
const std::size_t height,
166+
const std::size_t width,
167+
const std::size_t clip_z_lo,
168+
const std::size_t clip_z_hi,
169+
const std::size_t clip_y_lo,
170+
const std::size_t clip_y_hi,
171+
const std::size_t clip_x_lo,
172+
const std::size_t clip_x_hi,
173+
const Body &body
174+
) {
175+
std::size_t z_lo_v, z_hi_v, y_lo_v, y_hi_v, x_lo_v, x_hi_v;
176+
valid_axis_range(dz, depth, z_lo_v, z_hi_v);
177+
valid_axis_range(dy, height, y_lo_v, y_hi_v);
178+
valid_axis_range(dx, width, x_lo_v, x_hi_v);
179+
const auto z_lo = std::max(z_lo_v, clip_z_lo);
180+
const auto z_hi = std::min(z_hi_v, clip_z_hi);
181+
const auto y_lo = std::max(y_lo_v, clip_y_lo);
182+
const auto y_hi = std::min(y_hi_v, clip_y_hi);
183+
const auto x_lo = std::max(x_lo_v, clip_x_lo);
184+
const auto x_hi = std::min(x_hi_v, clip_x_hi);
185+
if (z_lo >= z_hi || y_lo >= y_hi || x_lo >= x_hi) {
186+
return;
187+
}
188+
const auto slice_size = height * width;
189+
const auto offset_stride =
190+
dz * static_cast<std::ptrdiff_t>(slice_size) +
191+
dy * static_cast<std::ptrdiff_t>(width) + dx;
192+
for (std::size_t z = z_lo; z < z_hi; ++z) {
193+
const auto slice_offset = z * slice_size;
194+
for (std::size_t y = y_lo; y < y_hi; ++y) {
195+
const auto row_offset = slice_offset + y * width;
196+
for (std::size_t x = x_lo; x < x_hi; ++x) {
197+
const auto node = row_offset + x;
198+
const auto target = static_cast<std::uint64_t>(
199+
static_cast<std::ptrdiff_t>(node) + offset_stride
200+
);
201+
body(static_cast<std::uint64_t>(node), target);
202+
}
203+
}
204+
}
205+
}
206+
112207
} // namespace bioimage_cpp::detail

include/bioimage_cpp/graph/distributed/block_extraction.hxx

Lines changed: 25 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ using bioimage_cpp::detail::checked_label_to_node;
9494
using bioimage_cpp::detail::Edge;
9595
using bioimage_cpp::detail::EdgeHash;
9696
using bioimage_cpp::detail::edge_key;
97-
using bioimage_cpp::detail::valid_axis_range;
9897

9998
using EdgeSet = std::unordered_set<Edge, EdgeHash>;
10099
using EdgeStatsMap = std::unordered_map<Edge, PartialStats, EdgeHash>;
@@ -152,104 +151,14 @@ inline void validate_owned_box(
152151
}
153152
}
154153

155-
// Sweep reference nodes over the owned box on a 2D grid, calling
156-
// `body(node, target)` with flat C-order indices into the outer array for each
157-
// reference node whose `+offset` neighbor stays inside the outer array. Axis 0
158-
// is additionally restricted to the absolute slab `[slab_begin, slab_end)`
159-
// (the caller's thread chunk, already inside the owned box).
160-
template <class Body>
161-
void sweep_owned_box_2d(
162-
const std::ptrdiff_t dy,
163-
const std::ptrdiff_t dx,
164-
const std::size_t outer_h,
165-
const std::size_t outer_w,
166-
const std::int64_t own_begin_y,
167-
const std::int64_t own_begin_x,
168-
const std::int64_t own_shape_y,
169-
const std::int64_t own_shape_x,
170-
const std::size_t slab_begin,
171-
const std::size_t slab_end,
172-
const Body &body
173-
) {
174-
std::size_t y_lo_v, y_hi_v, x_lo_v, x_hi_v;
175-
valid_axis_range(dy, outer_h, y_lo_v, y_hi_v);
176-
valid_axis_range(dx, outer_w, x_lo_v, x_hi_v);
177-
178-
const auto y_lo = std::max({y_lo_v, static_cast<std::size_t>(own_begin_y), slab_begin});
179-
const auto y_hi = std::min({y_hi_v, static_cast<std::size_t>(own_begin_y + own_shape_y), slab_end});
180-
const auto x_lo = std::max(x_lo_v, static_cast<std::size_t>(own_begin_x));
181-
const auto x_hi = std::min(x_hi_v, static_cast<std::size_t>(own_begin_x + own_shape_x));
182-
if (y_lo >= y_hi || x_lo >= x_hi) {
183-
return;
184-
}
185-
186-
const auto offset_stride = dy * static_cast<std::ptrdiff_t>(outer_w) + dx;
187-
for (std::size_t y = y_lo; y < y_hi; ++y) {
188-
const auto row_offset = y * outer_w;
189-
for (std::size_t x = x_lo; x < x_hi; ++x) {
190-
const auto node = row_offset + x;
191-
const auto target = static_cast<std::uint64_t>(
192-
static_cast<std::ptrdiff_t>(node) + offset_stride
193-
);
194-
body(static_cast<std::uint64_t>(node), target);
195-
}
196-
}
197-
}
198-
199-
// 3D variant of `sweep_owned_box_2d`.
200-
template <class Body>
201-
void sweep_owned_box_3d(
202-
const std::ptrdiff_t dz,
203-
const std::ptrdiff_t dy,
204-
const std::ptrdiff_t dx,
205-
const std::size_t outer_d,
206-
const std::size_t outer_h,
207-
const std::size_t outer_w,
208-
const std::int64_t own_begin_z,
209-
const std::int64_t own_begin_y,
210-
const std::int64_t own_begin_x,
211-
const std::int64_t own_shape_z,
212-
const std::int64_t own_shape_y,
213-
const std::int64_t own_shape_x,
214-
const std::size_t slab_begin,
215-
const std::size_t slab_end,
216-
const Body &body
217-
) {
218-
std::size_t z_lo_v, z_hi_v, y_lo_v, y_hi_v, x_lo_v, x_hi_v;
219-
valid_axis_range(dz, outer_d, z_lo_v, z_hi_v);
220-
valid_axis_range(dy, outer_h, y_lo_v, y_hi_v);
221-
valid_axis_range(dx, outer_w, x_lo_v, x_hi_v);
222-
223-
const auto z_lo = std::max({z_lo_v, static_cast<std::size_t>(own_begin_z), slab_begin});
224-
const auto z_hi = std::min({z_hi_v, static_cast<std::size_t>(own_begin_z + own_shape_z), slab_end});
225-
const auto y_lo = std::max(y_lo_v, static_cast<std::size_t>(own_begin_y));
226-
const auto y_hi = std::min(y_hi_v, static_cast<std::size_t>(own_begin_y + own_shape_y));
227-
const auto x_lo = std::max(x_lo_v, static_cast<std::size_t>(own_begin_x));
228-
const auto x_hi = std::min(x_hi_v, static_cast<std::size_t>(own_begin_x + own_shape_x));
229-
if (z_lo >= z_hi || y_lo >= y_hi || x_lo >= x_hi) {
230-
return;
231-
}
232-
233-
const auto slice_size = outer_h * outer_w;
234-
const auto offset_stride =
235-
dz * static_cast<std::ptrdiff_t>(slice_size) +
236-
dy * static_cast<std::ptrdiff_t>(outer_w) + dx;
237-
for (std::size_t z = z_lo; z < z_hi; ++z) {
238-
const auto slice_offset = z * slice_size;
239-
for (std::size_t y = y_lo; y < y_hi; ++y) {
240-
const auto row_offset = slice_offset + y * outer_w;
241-
for (std::size_t x = x_lo; x < x_hi; ++x) {
242-
const auto node = row_offset + x;
243-
const auto target = static_cast<std::uint64_t>(
244-
static_cast<std::ptrdiff_t>(node) + offset_stride
245-
);
246-
body(static_cast<std::uint64_t>(node), target);
247-
}
248-
}
249-
}
250-
}
251-
252-
// Dispatch the owned-box sweep for one offset over the correct grid rank.
154+
// Dispatch the owned-box sweep for one offset over the correct grid rank:
155+
// `body(node, target)` is called with flat C-order indices into the outer
156+
// array for each reference node inside the owned box whose `+offset` neighbor
157+
// stays inside the outer array. Axis 0 is additionally restricted to the
158+
// absolute slab `[slab_begin, slab_end)` (the caller's thread chunk, already
159+
// inside the owned box). The loop itself is the shared
160+
// `detail/grid.hxx::sweep_clipped_box_{2d,3d}`; the owned box and slab fold
161+
// into its clip box.
253162
template <class Body>
254163
void sweep_owned_box(
255164
const std::vector<std::ptrdiff_t> &offset,
@@ -260,18 +169,27 @@ void sweep_owned_box(
260169
const std::size_t slab_end,
261170
const Body &body
262171
) {
172+
const auto lo = [&](const std::size_t axis) {
173+
return static_cast<std::size_t>(own_begin[axis]);
174+
};
175+
const auto hi = [&](const std::size_t axis) {
176+
return static_cast<std::size_t>(own_begin[axis] + own_shape[axis]);
177+
};
263178
if (outer_dims.size() == 2) {
264-
sweep_owned_box_2d(
179+
bioimage_cpp::detail::sweep_clipped_box_2d(
265180
offset[0], offset[1], outer_dims[0], outer_dims[1],
266-
own_begin[0], own_begin[1], own_shape[0], own_shape[1],
267-
slab_begin, slab_end, body
181+
std::max(lo(0), slab_begin), std::min(hi(0), slab_end),
182+
lo(1), hi(1),
183+
body
268184
);
269185
} else {
270-
sweep_owned_box_3d(
271-
offset[0], offset[1], offset[2], outer_dims[0], outer_dims[1], outer_dims[2],
272-
own_begin[0], own_begin[1], own_begin[2],
273-
own_shape[0], own_shape[1], own_shape[2],
274-
slab_begin, slab_end, body
186+
bioimage_cpp::detail::sweep_clipped_box_3d(
187+
offset[0], offset[1], offset[2],
188+
outer_dims[0], outer_dims[1], outer_dims[2],
189+
std::max(lo(0), slab_begin), std::min(hi(0), slab_end),
190+
lo(1), hi(1),
191+
lo(2), hi(2),
192+
body
275193
);
276194
}
277195
}

include/bioimage_cpp/graph/feature_accumulation.hxx

Lines changed: 9 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -193,16 +193,10 @@ void scan_edge_map_3d_chunk(
193193
}
194194
}
195195

196-
// `valid_axis_range` lives in `detail/grid.hxx` (it is pure grid geometry,
197-
// shared with the distributed block-extraction sweeps). Pull it into this
198-
// namespace so the offset-box sweeps below can call it unqualified.
199-
using bioimage_cpp::detail::valid_axis_range;
200-
201196
// Sweep every (node, target) pair on a 2D grid for which `node + offset` stays
202-
// in bounds, restricted to the half-open y-slab [y_begin, y_end). The body
203-
// receives flat C-order indices for both endpoints and is expected to inline
204-
// at -O2 since this is a header-only template with a fully-known callable
205-
// type at instantiation.
197+
// in bounds, restricted to the half-open y-slab [y_begin, y_end). Thin wrapper
198+
// around `detail/grid.hxx::sweep_clipped_box_2d` (the loop shared with the
199+
// distributed block extraction) with the x-axis unclipped.
206200
template <class Body>
207201
void sweep_offset_box_2d(
208202
const std::ptrdiff_t dy,
@@ -213,25 +207,9 @@ void sweep_offset_box_2d(
213207
const std::size_t y_end,
214208
const Body &body
215209
) {
216-
std::size_t y_lo_full, y_hi_full, x_lo, x_hi;
217-
valid_axis_range(dy, height, y_lo_full, y_hi_full);
218-
valid_axis_range(dx, width, x_lo, x_hi);
219-
const auto y_lo = std::max(y_lo_full, y_begin);
220-
const auto y_hi = std::min(y_hi_full, y_end);
221-
if (y_lo >= y_hi || x_lo >= x_hi) {
222-
return;
223-
}
224-
const auto offset_stride = dy * static_cast<std::ptrdiff_t>(width) + dx;
225-
for (std::size_t y = y_lo; y < y_hi; ++y) {
226-
const auto row_offset = y * width;
227-
for (std::size_t x = x_lo; x < x_hi; ++x) {
228-
const auto node = row_offset + x;
229-
const auto target = static_cast<std::uint64_t>(
230-
static_cast<std::ptrdiff_t>(node) + offset_stride
231-
);
232-
body(static_cast<std::uint64_t>(node), target);
233-
}
234-
}
210+
bioimage_cpp::detail::sweep_clipped_box_2d(
211+
dy, dx, height, width, y_begin, y_end, 0, width, body
212+
);
235213
}
236214

237215
// 3D variant of `sweep_offset_box_2d`. Restricts the sweep to a z-slab.
@@ -247,32 +225,9 @@ void sweep_offset_box_3d(
247225
const std::size_t z_end,
248226
const Body &body
249227
) {
250-
std::size_t z_lo_full, z_hi_full, y_lo, y_hi, x_lo, x_hi;
251-
valid_axis_range(dz, depth, z_lo_full, z_hi_full);
252-
valid_axis_range(dy, height, y_lo, y_hi);
253-
valid_axis_range(dx, width, x_lo, x_hi);
254-
const auto z_lo = std::max(z_lo_full, z_begin);
255-
const auto z_hi = std::min(z_hi_full, z_end);
256-
if (z_lo >= z_hi || y_lo >= y_hi || x_lo >= x_hi) {
257-
return;
258-
}
259-
const auto slice_size = height * width;
260-
const auto offset_stride =
261-
dz * static_cast<std::ptrdiff_t>(slice_size) +
262-
dy * static_cast<std::ptrdiff_t>(width) + dx;
263-
for (std::size_t z = z_lo; z < z_hi; ++z) {
264-
const auto slice_offset = z * slice_size;
265-
for (std::size_t y = y_lo; y < y_hi; ++y) {
266-
const auto row_offset = slice_offset + y * width;
267-
for (std::size_t x = x_lo; x < x_hi; ++x) {
268-
const auto node = row_offset + x;
269-
const auto target = static_cast<std::uint64_t>(
270-
static_cast<std::ptrdiff_t>(node) + offset_stride
271-
);
272-
body(static_cast<std::uint64_t>(node), target);
273-
}
274-
}
275-
}
228+
bioimage_cpp::detail::sweep_clipped_box_3d(
229+
dz, dy, dx, depth, height, width, z_begin, z_end, 0, height, 0, width, body
230+
);
276231
}
277232

278233
template <class LabelT, class ValueT, class Stats>

0 commit comments

Comments
 (0)