|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include "bioimage_cpp/array_view.hxx" |
| 4 | + |
| 5 | +#include <algorithm> |
| 6 | +#include <cstddef> |
| 7 | +#include <cstdint> |
| 8 | +#include <numeric> |
| 9 | +#include <stdexcept> |
| 10 | +#include <string> |
| 11 | +#include <unordered_map> |
| 12 | +#include <unordered_set> |
| 13 | +#include <vector> |
| 14 | + |
| 15 | +namespace bioimage_cpp { |
| 16 | + |
| 17 | +class DisjointSets { |
| 18 | +public: |
| 19 | + explicit DisjointSets(const std::size_t size) : parents_(size), ranks_(size, 0) { |
| 20 | + std::iota(parents_.begin(), parents_.end(), std::uint64_t{0}); |
| 21 | + } |
| 22 | + |
| 23 | + std::uint64_t find(const std::uint64_t node) { |
| 24 | + if (parents_[node] != node) { |
| 25 | + parents_[node] = find(parents_[node]); |
| 26 | + } |
| 27 | + return parents_[node]; |
| 28 | + } |
| 29 | + |
| 30 | + std::uint64_t unite_roots(std::uint64_t first, std::uint64_t second) { |
| 31 | + if (ranks_[first] < ranks_[second]) { |
| 32 | + std::swap(first, second); |
| 33 | + } |
| 34 | + parents_[second] = first; |
| 35 | + if (ranks_[first] == ranks_[second]) { |
| 36 | + ++ranks_[first]; |
| 37 | + } |
| 38 | + return first; |
| 39 | + } |
| 40 | + |
| 41 | +private: |
| 42 | + std::vector<std::uint64_t> parents_; |
| 43 | + std::vector<std::uint64_t> ranks_; |
| 44 | +}; |
| 45 | + |
| 46 | +using MutexStorage = std::vector<std::unordered_set<std::uint64_t>>; |
| 47 | + |
| 48 | +inline bool check_mutex( |
| 49 | + const std::uint64_t first, |
| 50 | + const std::uint64_t second, |
| 51 | + const MutexStorage &mutexes |
| 52 | +) { |
| 53 | + const auto &first_mutexes = mutexes[first]; |
| 54 | + const auto &second_mutexes = mutexes[second]; |
| 55 | + if (first_mutexes.size() < second_mutexes.size()) { |
| 56 | + return first_mutexes.find(second) != first_mutexes.end(); |
| 57 | + } |
| 58 | + return second_mutexes.find(first) != second_mutexes.end(); |
| 59 | +} |
| 60 | + |
| 61 | +inline void insert_mutex( |
| 62 | + const std::uint64_t first, |
| 63 | + const std::uint64_t second, |
| 64 | + MutexStorage &mutexes |
| 65 | +) { |
| 66 | + mutexes[first].insert(second); |
| 67 | + mutexes[second].insert(first); |
| 68 | +} |
| 69 | + |
| 70 | +inline void merge_mutexes( |
| 71 | + const std::uint64_t root_from, |
| 72 | + const std::uint64_t root_to, |
| 73 | + MutexStorage &mutexes |
| 74 | +) { |
| 75 | + auto &mutexes_from = mutexes[root_from]; |
| 76 | + auto &mutexes_to = mutexes[root_to]; |
| 77 | + |
| 78 | + for (const auto other_root : mutexes_from) { |
| 79 | + auto &other_mutexes = mutexes[other_root]; |
| 80 | + other_mutexes.erase(root_from); |
| 81 | + if (other_root != root_to) { |
| 82 | + other_mutexes.insert(root_to); |
| 83 | + mutexes_to.insert(other_root); |
| 84 | + } |
| 85 | + } |
| 86 | + mutexes_to.erase(root_from); |
| 87 | + mutexes_to.erase(root_to); |
| 88 | + mutexes_from.clear(); |
| 89 | +} |
| 90 | + |
| 91 | +inline std::vector<std::ptrdiff_t> c_order_strides(const std::vector<std::ptrdiff_t> &shape) { |
| 92 | + std::vector<std::ptrdiff_t> strides(shape.size(), 1); |
| 93 | + for (std::ptrdiff_t axis = static_cast<std::ptrdiff_t>(shape.size()) - 2; axis >= 0; --axis) { |
| 94 | + strides[static_cast<std::size_t>(axis)] = |
| 95 | + strides[static_cast<std::size_t>(axis + 1)] * shape[static_cast<std::size_t>(axis + 1)]; |
| 96 | + } |
| 97 | + return strides; |
| 98 | +} |
| 99 | + |
| 100 | +inline bool is_valid_grid_edge( |
| 101 | + const std::uint64_t node, |
| 102 | + const std::vector<std::ptrdiff_t> &offset, |
| 103 | + const std::vector<std::ptrdiff_t> &shape, |
| 104 | + const std::vector<std::ptrdiff_t> &strides |
| 105 | +) { |
| 106 | + for (std::size_t axis = 0; axis < shape.size(); ++axis) { |
| 107 | + const auto coord = static_cast<std::ptrdiff_t>(node / static_cast<std::uint64_t>(strides[axis])) % shape[axis]; |
| 108 | + const auto neighbor = coord + offset[axis]; |
| 109 | + if (neighbor < 0 || neighbor >= shape[axis]) { |
| 110 | + return false; |
| 111 | + } |
| 112 | + } |
| 113 | + return true; |
| 114 | +} |
| 115 | + |
| 116 | +template <class T> |
| 117 | +void mutex_watershed_grid( |
| 118 | + const ConstArrayView<T> &affinities, |
| 119 | + const std::vector<std::vector<std::ptrdiff_t>> &offsets, |
| 120 | + const std::size_t number_of_attractive_channels, |
| 121 | + const ArrayView<std::uint64_t> &out |
| 122 | +) { |
| 123 | + if (affinities.ndim() != 3 && affinities.ndim() != 4) { |
| 124 | + throw std::invalid_argument( |
| 125 | + "affinities must have shape (channels, y, x) or (channels, z, y, x), got ndim=" + |
| 126 | + std::to_string(affinities.ndim()) |
| 127 | + ); |
| 128 | + } |
| 129 | + if (offsets.empty()) { |
| 130 | + throw std::invalid_argument("offsets must not be empty"); |
| 131 | + } |
| 132 | + |
| 133 | + const auto number_of_channels = static_cast<std::size_t>(affinities.shape[0]); |
| 134 | + const auto spatial_ndim = static_cast<std::size_t>(affinities.ndim() - 1); |
| 135 | + if (offsets.size() != number_of_channels) { |
| 136 | + throw std::invalid_argument( |
| 137 | + "offsets length must match affinities channel count, got offsets length=" + |
| 138 | + std::to_string(offsets.size()) + ", channels=" + std::to_string(number_of_channels) |
| 139 | + ); |
| 140 | + } |
| 141 | + if (number_of_attractive_channels > number_of_channels) { |
| 142 | + throw std::invalid_argument("number_of_attractive_channels must be <= number of channels"); |
| 143 | + } |
| 144 | + for (const auto &offset : offsets) { |
| 145 | + if (offset.size() != spatial_ndim) { |
| 146 | + throw std::invalid_argument( |
| 147 | + "each offset must have length matching the spatial ndim, got spatial ndim=" + |
| 148 | + std::to_string(spatial_ndim) |
| 149 | + ); |
| 150 | + } |
| 151 | + } |
| 152 | + |
| 153 | + std::vector<std::ptrdiff_t> spatial_shape( |
| 154 | + affinities.shape.begin() + 1, |
| 155 | + affinities.shape.end() |
| 156 | + ); |
| 157 | + if (out.shape != spatial_shape) { |
| 158 | + throw std::invalid_argument("out shape must match affinities spatial shape"); |
| 159 | + } |
| 160 | + |
| 161 | + const auto number_of_nodes = static_cast<std::uint64_t>(std::accumulate( |
| 162 | + spatial_shape.begin(), |
| 163 | + spatial_shape.end(), |
| 164 | + std::ptrdiff_t{1}, |
| 165 | + [](const std::ptrdiff_t a, const std::ptrdiff_t b) { return a * b; } |
| 166 | + )); |
| 167 | + const auto spatial_strides = c_order_strides(spatial_shape); |
| 168 | + |
| 169 | + std::vector<std::ptrdiff_t> offset_strides(number_of_channels, 0); |
| 170 | + for (std::size_t channel = 0; channel < number_of_channels; ++channel) { |
| 171 | + for (std::size_t axis = 0; axis < spatial_ndim; ++axis) { |
| 172 | + offset_strides[channel] += offsets[channel][axis] * spatial_strides[axis]; |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + const auto number_of_edges = number_of_nodes * number_of_channels; |
| 177 | + std::vector<std::uint64_t> edge_order(static_cast<std::size_t>(number_of_edges)); |
| 178 | + std::iota(edge_order.begin(), edge_order.end(), std::uint64_t{0}); |
| 179 | + std::sort(edge_order.begin(), edge_order.end(), [&](const std::uint64_t first, const std::uint64_t second) { |
| 180 | + const T first_weight = affinities.data[first]; |
| 181 | + const T second_weight = affinities.data[second]; |
| 182 | + if (first_weight == second_weight) { |
| 183 | + return first < second; |
| 184 | + } |
| 185 | + return first_weight > second_weight; |
| 186 | + }); |
| 187 | + |
| 188 | + DisjointSets sets(static_cast<std::size_t>(number_of_nodes)); |
| 189 | + MutexStorage mutexes(static_cast<std::size_t>(number_of_nodes)); |
| 190 | + |
| 191 | + for (const auto edge_id : edge_order) { |
| 192 | + const auto channel = static_cast<std::size_t>(edge_id / number_of_nodes); |
| 193 | + const auto u = edge_id % number_of_nodes; |
| 194 | + if (!is_valid_grid_edge(u, offsets[channel], spatial_shape, spatial_strides)) { |
| 195 | + continue; |
| 196 | + } |
| 197 | + |
| 198 | + const auto v_signed = static_cast<std::int64_t>(u) + static_cast<std::int64_t>(offset_strides[channel]); |
| 199 | + const auto v = static_cast<std::uint64_t>(v_signed); |
| 200 | + std::uint64_t root_u = sets.find(u); |
| 201 | + std::uint64_t root_v = sets.find(v); |
| 202 | + if (root_u == root_v || check_mutex(root_u, root_v, mutexes)) { |
| 203 | + continue; |
| 204 | + } |
| 205 | + |
| 206 | + const bool is_mutex_edge = channel >= number_of_attractive_channels; |
| 207 | + if (is_mutex_edge) { |
| 208 | + insert_mutex(root_u, root_v, mutexes); |
| 209 | + } else { |
| 210 | + const auto new_root = sets.unite_roots(root_u, root_v); |
| 211 | + const auto old_root = (new_root == root_u) ? root_v : root_u; |
| 212 | + merge_mutexes(old_root, new_root, mutexes); |
| 213 | + } |
| 214 | + } |
| 215 | + |
| 216 | + std::unordered_map<std::uint64_t, std::uint64_t> label_map; |
| 217 | + for (std::uint64_t node = 0; node < number_of_nodes; ++node) { |
| 218 | + const auto root = sets.find(node); |
| 219 | + auto found = label_map.find(root); |
| 220 | + if (found == label_map.end()) { |
| 221 | + const auto next_label = static_cast<std::uint64_t>(label_map.size() + 1); |
| 222 | + found = label_map.emplace(root, next_label).first; |
| 223 | + } |
| 224 | + out.data[node] = found->second; |
| 225 | + } |
| 226 | +} |
| 227 | + |
| 228 | +} // namespace bioimage_cpp |
0 commit comments