|
2 | 2 |
|
3 | 3 | #include "bioimage_cpp/array_view.hxx" |
4 | 4 | #include "bioimage_cpp/detail/grid.hxx" |
| 5 | +#include "bioimage_cpp/detail/profile.hxx" |
5 | 6 | #include "bioimage_cpp/detail/threading.hxx" |
6 | 7 | #include "bioimage_cpp/graph/region_adjacency_graph.hxx" |
7 | 8 |
|
@@ -204,31 +205,168 @@ void scan_edge_map_3d_chunk( |
204 | 205 | } |
205 | 206 | } |
206 | 207 |
|
| 208 | +// Given an offset along one axis and the axis length, return the half-open |
| 209 | +// range of axis coordinates `[lo, hi)` for which `coord + delta` stays in |
| 210 | +// `[0, length)`. Returns `lo >= hi` if the offset is larger than the axis. |
| 211 | +inline void valid_axis_range( |
| 212 | + const std::ptrdiff_t delta, |
| 213 | + const std::size_t length, |
| 214 | + std::size_t &lo, |
| 215 | + std::size_t &hi |
| 216 | +) { |
| 217 | + if (delta >= 0) { |
| 218 | + lo = 0; |
| 219 | + const auto d = static_cast<std::size_t>(delta); |
| 220 | + hi = (d >= length) ? 0 : (length - d); |
| 221 | + } else { |
| 222 | + const auto d = static_cast<std::size_t>(-delta); |
| 223 | + lo = (d >= length) ? length : d; |
| 224 | + hi = length; |
| 225 | + } |
| 226 | +} |
| 227 | + |
| 228 | +// Sweep every (node, target) pair on a 2D grid for which `node + offset` stays |
| 229 | +// in bounds, restricted to the half-open y-slab [y_begin, y_end). The body |
| 230 | +// receives flat C-order indices for both endpoints and is expected to inline |
| 231 | +// at -O2 since this is a header-only template with a fully-known callable |
| 232 | +// type at instantiation. |
| 233 | +template <class Body> |
| 234 | +void sweep_offset_box_2d( |
| 235 | + const std::ptrdiff_t dy, |
| 236 | + const std::ptrdiff_t dx, |
| 237 | + const std::size_t height, |
| 238 | + const std::size_t width, |
| 239 | + const std::size_t y_begin, |
| 240 | + const std::size_t y_end, |
| 241 | + const Body &body |
| 242 | +) { |
| 243 | + std::size_t y_lo_full, y_hi_full, x_lo, x_hi; |
| 244 | + valid_axis_range(dy, height, y_lo_full, y_hi_full); |
| 245 | + valid_axis_range(dx, width, x_lo, x_hi); |
| 246 | + const auto y_lo = std::max(y_lo_full, y_begin); |
| 247 | + const auto y_hi = std::min(y_hi_full, y_end); |
| 248 | + if (y_lo >= y_hi || x_lo >= x_hi) { |
| 249 | + return; |
| 250 | + } |
| 251 | + const auto offset_stride = dy * static_cast<std::ptrdiff_t>(width) + dx; |
| 252 | + for (std::size_t y = y_lo; y < y_hi; ++y) { |
| 253 | + const auto row_offset = y * width; |
| 254 | + for (std::size_t x = x_lo; x < x_hi; ++x) { |
| 255 | + const auto node = row_offset + x; |
| 256 | + const auto target = static_cast<std::uint64_t>( |
| 257 | + static_cast<std::ptrdiff_t>(node) + offset_stride |
| 258 | + ); |
| 259 | + body(static_cast<std::uint64_t>(node), target); |
| 260 | + } |
| 261 | + } |
| 262 | +} |
| 263 | + |
| 264 | +// 3D variant of `sweep_offset_box_2d`. Restricts the sweep to a z-slab. |
| 265 | +template <class Body> |
| 266 | +void sweep_offset_box_3d( |
| 267 | + const std::ptrdiff_t dz, |
| 268 | + const std::ptrdiff_t dy, |
| 269 | + const std::ptrdiff_t dx, |
| 270 | + const std::size_t depth, |
| 271 | + const std::size_t height, |
| 272 | + const std::size_t width, |
| 273 | + const std::size_t z_begin, |
| 274 | + const std::size_t z_end, |
| 275 | + const Body &body |
| 276 | +) { |
| 277 | + std::size_t z_lo_full, z_hi_full, y_lo, y_hi, x_lo, x_hi; |
| 278 | + valid_axis_range(dz, depth, z_lo_full, z_hi_full); |
| 279 | + valid_axis_range(dy, height, y_lo, y_hi); |
| 280 | + valid_axis_range(dx, width, x_lo, x_hi); |
| 281 | + const auto z_lo = std::max(z_lo_full, z_begin); |
| 282 | + const auto z_hi = std::min(z_hi_full, z_end); |
| 283 | + if (z_lo >= z_hi || y_lo >= y_hi || x_lo >= x_hi) { |
| 284 | + return; |
| 285 | + } |
| 286 | + const auto slice_size = height * width; |
| 287 | + const auto offset_stride = |
| 288 | + dz * static_cast<std::ptrdiff_t>(slice_size) + |
| 289 | + dy * static_cast<std::ptrdiff_t>(width) + dx; |
| 290 | + for (std::size_t z = z_lo; z < z_hi; ++z) { |
| 291 | + const auto slice_offset = z * slice_size; |
| 292 | + for (std::size_t y = y_lo; y < y_hi; ++y) { |
| 293 | + const auto row_offset = slice_offset + y * width; |
| 294 | + for (std::size_t x = x_lo; x < x_hi; ++x) { |
| 295 | + const auto node = row_offset + x; |
| 296 | + const auto target = static_cast<std::uint64_t>( |
| 297 | + static_cast<std::ptrdiff_t>(node) + offset_stride |
| 298 | + ); |
| 299 | + body(static_cast<std::uint64_t>(node), target); |
| 300 | + } |
| 301 | + } |
| 302 | + } |
| 303 | +} |
| 304 | + |
207 | 305 | template <class LabelT, class ValueT, class Stats> |
208 | | -void scan_affinity_chunk( |
| 306 | +void scan_affinity_2d_chunk( |
209 | 307 | const RegionAdjacencyGraph &rag, |
210 | 308 | const LabelT *labels, |
211 | 309 | const ValueT *affinities, |
212 | 310 | const std::vector<std::vector<std::ptrdiff_t>> &offsets, |
213 | | - const std::vector<std::ptrdiff_t> &shape, |
214 | | - const std::size_t node_begin, |
215 | | - const std::size_t node_end, |
| 311 | + const std::size_t height, |
| 312 | + const std::size_t width, |
| 313 | + const std::size_t y_begin, |
| 314 | + const std::size_t y_end, |
216 | 315 | std::vector<Stats> &stats |
217 | 316 | ) { |
218 | | - const auto spatial_strides = bioimage_cpp::detail::c_order_strides(shape); |
219 | | - const auto number_of_nodes = static_cast<std::uint64_t>(number_of_pixels(shape)); |
| 317 | + const auto number_of_nodes = static_cast<std::uint64_t>(height * width); |
220 | 318 | for (std::size_t channel = 0; channel < offsets.size(); ++channel) { |
221 | | - const auto channel_offset = static_cast<std::uint64_t>(channel) * number_of_nodes; |
222 | | - for (std::uint64_t node = node_begin; node < node_end; ++node) { |
223 | | - std::uint64_t target = 0; |
224 | | - if (!bioimage_cpp::detail::valid_offset_target(node, offsets[channel], shape, spatial_strides, target)) { |
225 | | - continue; |
| 319 | + const auto &off = offsets[channel]; |
| 320 | + const auto channel_offset = |
| 321 | + static_cast<std::uint64_t>(channel) * number_of_nodes; |
| 322 | + sweep_offset_box_2d( |
| 323 | + off[0], off[1], height, width, y_begin, y_end, |
| 324 | + [&](const std::uint64_t node, const std::uint64_t target) { |
| 325 | + const auto u = label_at(labels, node); |
| 326 | + const auto v = label_at(labels, target); |
| 327 | + const auto edge = edge_for_labels(rag, u, v); |
| 328 | + if (edge >= 0) { |
| 329 | + stats[static_cast<std::size_t>(edge)].add( |
| 330 | + affinities[channel_offset + node] |
| 331 | + ); |
| 332 | + } |
226 | 333 | } |
227 | | - const auto edge = edge_for_labels(rag, label_at(labels, node), label_at(labels, target)); |
228 | | - if (edge >= 0) { |
229 | | - stats[static_cast<std::size_t>(edge)].add(affinities[channel_offset + node]); |
| 334 | + ); |
| 335 | + } |
| 336 | +} |
| 337 | + |
| 338 | +template <class LabelT, class ValueT, class Stats> |
| 339 | +void scan_affinity_3d_chunk( |
| 340 | + const RegionAdjacencyGraph &rag, |
| 341 | + const LabelT *labels, |
| 342 | + const ValueT *affinities, |
| 343 | + const std::vector<std::vector<std::ptrdiff_t>> &offsets, |
| 344 | + const std::size_t depth, |
| 345 | + const std::size_t height, |
| 346 | + const std::size_t width, |
| 347 | + const std::size_t z_begin, |
| 348 | + const std::size_t z_end, |
| 349 | + std::vector<Stats> &stats |
| 350 | +) { |
| 351 | + const auto slice_size = height * width; |
| 352 | + const auto number_of_nodes = static_cast<std::uint64_t>(depth * slice_size); |
| 353 | + for (std::size_t channel = 0; channel < offsets.size(); ++channel) { |
| 354 | + const auto &off = offsets[channel]; |
| 355 | + const auto channel_offset = |
| 356 | + static_cast<std::uint64_t>(channel) * number_of_nodes; |
| 357 | + sweep_offset_box_3d( |
| 358 | + off[0], off[1], off[2], depth, height, width, z_begin, z_end, |
| 359 | + [&](const std::uint64_t node, const std::uint64_t target) { |
| 360 | + const auto u = label_at(labels, node); |
| 361 | + const auto v = label_at(labels, target); |
| 362 | + const auto edge = edge_for_labels(rag, u, v); |
| 363 | + if (edge >= 0) { |
| 364 | + stats[static_cast<std::size_t>(edge)].add( |
| 365 | + affinities[channel_offset + node] |
| 366 | + ); |
| 367 | + } |
230 | 368 | } |
231 | | - } |
| 369 | + ); |
232 | 370 | } |
233 | 371 | } |
234 | 372 |
|
@@ -408,40 +546,79 @@ void accumulate_affinity_features( |
408 | 546 | throw std::invalid_argument("out shape must be (number_of_edges, number_of_features)"); |
409 | 547 | } |
410 | 548 |
|
411 | | - const auto number_of_nodes = detail_features::number_of_pixels(labels.shape); |
412 | | - const auto n_threads = detail::normalize_thread_count(number_of_threads, number_of_nodes); |
| 549 | + const auto work_items = static_cast<std::size_t>(labels.shape[0]); |
| 550 | + const auto n_threads = detail::normalize_thread_count(number_of_threads, work_items); |
413 | 551 | const auto number_of_edges = static_cast<std::size_t>(rag.number_of_edges()); |
414 | 552 |
|
| 553 | + BIOIMAGE_PROFILE_INIT(aff_profiler); |
| 554 | + |
415 | 555 | const auto run_scan = [&](auto &per_thread_stats) { |
| 556 | + BIOIMAGE_PROFILE_SCOPE(aff_profiler, "aff:scan"); |
416 | 557 | bioimage_cpp::detail::parallel_for_chunks( |
417 | 558 | n_threads, |
418 | | - number_of_nodes, |
| 559 | + work_items, |
419 | 560 | [&](const std::size_t thread_id, const std::size_t begin, const std::size_t end) { |
420 | | - detail_features::scan_affinity_chunk( |
421 | | - rag, labels.data, affinities.data, offsets, labels.shape, |
422 | | - begin, end, per_thread_stats[thread_id] |
423 | | - ); |
| 561 | + if (labels.ndim() == 2) { |
| 562 | + detail_features::scan_affinity_2d_chunk( |
| 563 | + rag, labels.data, affinities.data, offsets, |
| 564 | + static_cast<std::size_t>(labels.shape[0]), |
| 565 | + static_cast<std::size_t>(labels.shape[1]), |
| 566 | + begin, end, per_thread_stats[thread_id] |
| 567 | + ); |
| 568 | + } else { |
| 569 | + detail_features::scan_affinity_3d_chunk( |
| 570 | + rag, labels.data, affinities.data, offsets, |
| 571 | + static_cast<std::size_t>(labels.shape[0]), |
| 572 | + static_cast<std::size_t>(labels.shape[1]), |
| 573 | + static_cast<std::size_t>(labels.shape[2]), |
| 574 | + begin, end, per_thread_stats[thread_id] |
| 575 | + ); |
| 576 | + } |
424 | 577 | } |
425 | 578 | ); |
426 | 579 | }; |
427 | 580 |
|
428 | 581 | if (compute_complex_features) { |
429 | | - std::vector<std::vector<detail_features::ComplexStats>> per_thread_stats( |
430 | | - n_threads, |
431 | | - std::vector<detail_features::ComplexStats>(number_of_edges) |
432 | | - ); |
| 582 | + std::vector<std::vector<detail_features::ComplexStats>> per_thread_stats; |
| 583 | + { |
| 584 | + BIOIMAGE_PROFILE_SCOPE(aff_profiler, "aff:alloc"); |
| 585 | + per_thread_stats.assign( |
| 586 | + n_threads, |
| 587 | + std::vector<detail_features::ComplexStats>(number_of_edges) |
| 588 | + ); |
| 589 | + } |
433 | 590 | run_scan(per_thread_stats); |
434 | | - auto stats = detail_features::merge_stats(per_thread_stats, number_of_edges); |
435 | | - detail_features::write_complex_features(stats, out); |
| 591 | + std::vector<detail_features::ComplexStats> stats; |
| 592 | + { |
| 593 | + BIOIMAGE_PROFILE_SCOPE(aff_profiler, "aff:merge"); |
| 594 | + stats = detail_features::merge_stats(per_thread_stats, number_of_edges); |
| 595 | + } |
| 596 | + { |
| 597 | + BIOIMAGE_PROFILE_SCOPE(aff_profiler, "aff:write"); |
| 598 | + detail_features::write_complex_features(stats, out); |
| 599 | + } |
436 | 600 | } else { |
437 | | - std::vector<std::vector<detail_features::SimpleStats>> per_thread_stats( |
438 | | - n_threads, |
439 | | - std::vector<detail_features::SimpleStats>(number_of_edges) |
440 | | - ); |
| 601 | + std::vector<std::vector<detail_features::SimpleStats>> per_thread_stats; |
| 602 | + { |
| 603 | + BIOIMAGE_PROFILE_SCOPE(aff_profiler, "aff:alloc"); |
| 604 | + per_thread_stats.assign( |
| 605 | + n_threads, |
| 606 | + std::vector<detail_features::SimpleStats>(number_of_edges) |
| 607 | + ); |
| 608 | + } |
441 | 609 | run_scan(per_thread_stats); |
442 | | - auto stats = detail_features::merge_stats(per_thread_stats, number_of_edges); |
443 | | - detail_features::write_simple_features(stats, out); |
| 610 | + std::vector<detail_features::SimpleStats> stats; |
| 611 | + { |
| 612 | + BIOIMAGE_PROFILE_SCOPE(aff_profiler, "aff:merge"); |
| 613 | + stats = detail_features::merge_stats(per_thread_stats, number_of_edges); |
| 614 | + } |
| 615 | + { |
| 616 | + BIOIMAGE_PROFILE_SCOPE(aff_profiler, "aff:write"); |
| 617 | + detail_features::write_simple_features(stats, out); |
| 618 | + } |
444 | 619 | } |
| 620 | + |
| 621 | + BIOIMAGE_PROFILE_REPORT(aff_profiler); |
445 | 622 | } |
446 | 623 |
|
447 | 624 | } // namespace bioimage_cpp::graph |
0 commit comments