11 constexpr int32_t dx8[] = { 1, 1, 0, -1, -1, -1, 0, 1 };
12 constexpr int32_t dy8[] = { 0, 1, 1, 1, 0, -1, -1, -1 };
20 template <
typename Ins
ide>
21 std::vector<glm::ivec2> walk_boundary(
22 glm::ivec2 start,
size_t max_points, Inside inside)
24 std::vector<glm::ivec2>
points;
26 glm::ivec2 first_next {};
27 int32_t backtrack = 4;
29 while (
points.size() < max_points) {
30 int32_t direction = -1;
32 for (int32_t i = 1; i <= 8; ++i) {
33 const int32_t d = (backtrack + i) % 8;
34 next =
current + glm::ivec2(dx8[d], dy8[d]);
43 if (!
points.empty() &&
current == start && next == first_next)
50 backtrack = (direction + 6 - direction % 2) % 8;
56 Contour measure_contour(
57 const std::vector<glm::ivec2>&
pixels, uint32_t w, uint32_t
h,
61 float perimeter = 0.0F;
62 for (
size_t i = 0; i <
pixels.size(); ++i) {
65 area +=
a.x *
b.y -
b.x *
a.y;
66 perimeter += glm::length(
b -
a);
71 .area = (std::abs(area) * 0.5F + perimeter * 0.5F + 1.0F)
72 / (
static_cast<float>(w) *
static_cast<float>(
h)),
73 .perimeter = perimeter,
76 contour.points.reserve(
pixels.size());
77 const glm::vec2 extent(
static_cast<float>(w),
static_cast<float>(
h));
78 for (
const auto& p :
pixels)
79 contour.
points.push_back(glm::
vec2(p) / extent);
83 bool point_in_contour(
const std::vector<glm::vec2>& pts,
float px,
float py)
noexcept
86 const size_t n = pts.size();
87 for (
size_t i = 0; i < n; ++i) {
88 const glm::vec2
a = pts[i];
89 const glm::vec2
b = pts[(i + 1) % n];
92 const float cross = (
b.x -
a.x) * (
py -
a.y)
93 - (
b.y -
a.y) * (
px -
a.x);
99 const float cross = (
b.x -
a.x) * (
py -
a.y)
100 - (
b.y -
a.y) * (
px -
a.x);
112 std::span<const float> mask, uint32_t w, uint32_t
h,
113 float min_area, uint32_t max_contours, uint32_t max_points_per_contour)
115 if (w == 0 ||
h == 0)
119 return find_contours(cc, w,
h, min_area, max_contours, max_points_per_contour);
124 float min_area, uint32_t max_contours, uint32_t max_points_per_contour)
126 if (w == 0 ||
h == 0 || cc.
count == 0)
129 const size_t n =
static_cast<size_t>(w) *
h;
130 std::vector<TraceSeed> seeds(cc.
count, TraceSeed { .pixel = n, .label = 0, .parent = 0 });
131 for (
size_t i = 0; i < n; ++i) {
134 seeds[
label - 1] = { .pixel = i, .label =
label, .parent = 0 };
137 std::vector<uint32_t> background(n, 0);
138 std::vector<size_t> pending;
140 for (
size_t seed = 0; seed < n; ++seed) {
141 if (cc.
label_map[seed] != 0 || background[seed] != 0)
146 pending.push_back(seed);
147 background[seed] = region;
148 bool exterior =
false;
149 for (
size_t head = 0; head < pending.size(); ++head) {
150 const size_t pixel = pending[head];
151 const auto x =
static_cast<uint32_t
>(
pixel % w);
152 const auto y =
static_cast<uint32_t
>(
pixel / w);
153 exterior = exterior || x == 0 || y == 0 || x + 1 == w || y + 1 ==
h;
155 const auto visit = [&](
size_t neighbor) {
156 if (cc.
label_map[neighbor] == 0 && background[neighbor] == 0) {
157 background[neighbor] = region;
158 pending.push_back(neighbor);
173 seeds.push_back({ .pixel = seed, .label = region, .parent =
parent });
177 std::vector<Contour> result(seeds.size());
178 std::vector<uint8_t> valid(seeds.size(), 0);
179 const size_t max_points = max_points_per_contour > 0
180 ?
static_cast<size_t>(max_points_per_contour)
183 std::for_each(std::execution::par_unseq,
184 std::views::iota(
size_t { 0 }, seeds.size()).begin(),
185 std::views::iota(
size_t { 0 }, seeds.size()).end(),
187 const auto& seed = seeds[ci];
188 const glm::ivec2 start(
static_cast<int32_t
>(seed.pixel % w),
static_cast<int32_t
>(seed.pixel / w));
189 const auto inside = [&](glm::ivec2 p) {
190 if (p.x < 0 || p.y < 0 ||
static_cast<uint32_t
>(p.x) >= w ||
static_cast<uint32_t
>(p.y) >=
h)
192 const size_t index =
static_cast<size_t>(p.y) * w +
static_cast<uint32_t
>(p.x);
193 return seed.parent == 0 ? cc.
label_map[
index] == seed.label : background[
index] == seed.label;
195 const auto points = walk_boundary(start, max_points, inside);
199 auto contour = measure_contour(
points, w,
h, seed.parent);
200 if (contour.area < min_area)
203 result[ci] = std::move(contour);
207 std::vector<Contour>
out;
208 out.reserve(seeds.size());
209 for (
size_t i = 0; i < seeds.size(); ++i) {
211 out.push_back(std::move(result[i]));
214 if (max_contours > 0) {
215 const size_t count = std::min(
out.size(),
static_cast<size_t>(max_contours));
216 std::partial_sort(
out.begin(),
217 out.begin() +
static_cast<ptrdiff_t
>(
count),
228 uint32_t w, uint32_t
h,
231 float origin_x,
float origin_y,
232 float scale_x,
float scale_y)
237 for (uint32_t row = 0; row <
h; ++row) {
238 const float py = origin_y + (
static_cast<float>(row) + 0.5F) * scale_y;
239 for (uint32_t col = 0; col < w; ++col) {
240 const float px = origin_x + (
static_cast<float>(col) + 0.5F) * scale_x;
241 if (!point_in_contour(contour.
points,
px,
py)) {
242 const size_t base = (
static_cast<size_t>(row) * w + col) *
channels;
243 for (uint32_t c = 0; c <
channels; ++c)
std::vector< uint32_t > parent
Connected component labelling on normalised float masks.
Contour extraction from binary float masks.
const std::vector< float > * pixels
std::vector< glm::vec2 > * points
std::vector< float > * out
ComponentResult connected_components(std::span< const float > mask, uint32_t w, uint32_t h)
Label connected foreground components in a binary mask.
std::vector< Contour > find_contours(std::span< const float > mask, uint32_t w, uint32_t h, float min_area, uint32_t max_contours, uint32_t max_points_per_contour)
Extract outer and hole contours from a binary mask.
void apply_contour_mask(std::span< float > pixels, uint32_t w, uint32_t h, uint32_t channels, const Contour &contour, float origin_x, float origin_y, float scale_x, float scale_y)
Zero all pixels in pixels that fall outside contour.
std::vector< uint32_t > label_map
Per-pixel labels; CPU executor output is empty unless export_labels is set.
uint32_t count
Number of components found.
Result of connected component labelling.
static constexpr uint32_t no_parent
Outer contour parent sentinel.
std::vector< glm::vec2 > points
Closed contour extracted from a binary mask.