• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paulmthompson / WhiskerToolbox / 17270491352

27 Aug 2025 02:57PM UTC coverage: 65.333%. Remained the same
17270491352

push

github

paulmthompson
Merge branch 'main' of https://github.com/paulmthompson/WhiskerToolbox

352 of 628 new or added lines in 92 files covered. (56.05%)

357 existing lines in 24 files now uncovered.

26429 of 40453 relevant lines covered (65.33%)

1119.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.75
/src/CoreGeometry/src/masks.cpp
1
#include "CoreGeometry/masks.hpp"
2

3
#include <algorithm>
4
#include <cmath>
5
#include <map>
6
#include <set>
7

8
Mask2D create_mask(std::vector<uint32_t> const & x, std::vector<uint32_t> const & y) {
122✔
9
    auto new_mask = Mask2D{};
122✔
10
    new_mask.reserve(x.size());// Reserve space to avoid reallocations
122✔
11

12
    for (std::size_t i = 0; i < x.size(); i++) {
611✔
13
        new_mask.push_back({x[i], y[i]});
489✔
14
    }
15

16
    return new_mask;
122✔
17
}
×
18

19
Mask2D create_mask(std::vector<float> const & x, std::vector<float> const & y) {
4✔
20
    auto new_mask = Mask2D{};
4✔
21
    new_mask.reserve(x.size());// Reserve space to avoid reallocations
4✔
22

23
    for (std::size_t i = 0; i < x.size(); i++) {
15✔
24
        // Round coordinates to nearest integers and ensure non-negative
25
        uint32_t rounded_x = static_cast<uint32_t>(std::max(0.0f, std::round(x[i])));
11✔
26
        uint32_t rounded_y = static_cast<uint32_t>(std::max(0.0f, std::round(y[i])));
11✔
27
        new_mask.push_back({rounded_x, rounded_y});
11✔
28
    }
29

30
    return new_mask;
4✔
31
}
×
32

33
std::pair<Point2D<uint32_t>, Point2D<uint32_t>> get_bounding_box(Mask2D const & mask) {
16✔
34
    uint32_t min_x = mask[0].x;
16✔
35
    uint32_t max_x = mask[0].x;
16✔
36
    uint32_t min_y = mask[0].y;
16✔
37
    uint32_t max_y = mask[0].y;
16✔
38

39
    for (auto const & point: mask) {
122✔
40
        min_x = std::min(min_x, point.x);
106✔
41
        max_x = std::max(max_x, point.x);
106✔
42
        min_y = std::min(min_y, point.y);
106✔
43
        max_y = std::max(max_y, point.y);
106✔
44
    }
45

46
    return {Point2D<uint32_t>{min_x, min_y}, Point2D<uint32_t>{max_x, max_y}};
32✔
47
}
48

49
std::vector<Point2D<uint32_t>> get_mask_outline(Mask2D const & mask) {
5✔
50
    if (mask.empty() || mask.size() < 2) {
5✔
51
        return {};
2✔
52
    }
53

54
    // Create maps to store extremal points
55
    std::map<uint32_t, uint32_t> max_y_for_x;// x -> max_y
3✔
56
    std::map<uint32_t, uint32_t> min_y_for_x;// x -> min_y
3✔
57
    std::map<uint32_t, uint32_t> max_x_for_y;// y -> max_x
3✔
58
    std::map<uint32_t, uint32_t> min_x_for_y;// y -> min_x
3✔
59

60
    // Find extremal points
61
    for (auto const & point: mask) {
26✔
62
        // Update max y for this x
63
        if (max_y_for_x.find(point.x) == max_y_for_x.end() || point.y > max_y_for_x[point.x]) {
23✔
64
            max_y_for_x[point.x] = point.y;
20✔
65
        }
66

67
        // Update min y for this x
68
        if (min_y_for_x.find(point.x) == min_y_for_x.end() || point.y < min_y_for_x[point.x]) {
23✔
69
            min_y_for_x[point.x] = point.y;
13✔
70
        }
71

72
        // Update max x for this y
73
        if (max_x_for_y.find(point.y) == max_x_for_y.end() || point.x > max_x_for_y[point.y]) {
23✔
74
            max_x_for_y[point.y] = point.x;
23✔
75
        }
76

77
        // Update min x for this y
78
        if (min_x_for_y.find(point.y) == min_x_for_y.end() || point.x < min_x_for_y[point.y]) {
23✔
79
            min_x_for_y[point.y] = point.x;
8✔
80
        }
81
    }
82

83
    // Collect all extremal points
84
    std::set<std::pair<uint32_t, uint32_t>> extremal_points_set;
3✔
85

86
    // Add max_y points for each x
87
    for (auto const & [x, max_y]: max_y_for_x) {
13✔
88
        extremal_points_set.insert({x, max_y});
10✔
89
    }
90

91
    // Add min_y points for each x
92
    for (auto const & [x, min_y]: min_y_for_x) {
13✔
93
        extremal_points_set.insert({x, min_y});
10✔
94
    }
95

96
    // Add max_x points for each y
97
    for (auto const & [y, max_x]: max_x_for_y) {
11✔
98
        extremal_points_set.insert({max_x, y});
8✔
99
    }
100

101
    // Add min_x points for each y
102
    for (auto const & [y, min_x]: min_x_for_y) {
11✔
103
        extremal_points_set.insert({min_x, y});
8✔
104
    }
105

106
    // Convert to vector and sort by angle from centroid for proper ordering
107
    std::vector<Point2D<uint32_t>> extremal_points;
3✔
108
    extremal_points.reserve(extremal_points_set.size());
3✔
109

110
    for (auto const & [x, y]: extremal_points_set) {
23✔
111
        extremal_points.push_back({x, y});
20✔
112
    }
113

114
    if (extremal_points.size() < 3) {
3✔
115
        return extremal_points;// Not enough points for proper outline
1✔
116
    }
117

118
    // Calculate centroid
119
    float centroid_x = 0.0f, centroid_y = 0.0f;
2✔
120
    for (auto const & point: extremal_points) {
20✔
121
        centroid_x += static_cast<float>(point.x);
18✔
122
        centroid_y += static_cast<float>(point.y);
18✔
123
    }
124
    centroid_x /= static_cast<float>(extremal_points.size());
2✔
125
    centroid_y /= static_cast<float>(extremal_points.size());
2✔
126

127
    // Sort points by angle from centroid (for proper connection order)
128
    std::sort(extremal_points.begin(), extremal_points.end(),
2✔
129
              [centroid_x, centroid_y](Point2D<uint32_t> const & a, Point2D<uint32_t> const & b) {
74✔
130
                  float const angle_a = std::atan2(static_cast<float>(a.y) - centroid_y, static_cast<float>(a.x) - centroid_x);
74✔
131
                  float const angle_b = std::atan2(static_cast<float>(b.y) - centroid_y, static_cast<float>(b.x) - centroid_x);
74✔
132
                  return angle_a < angle_b;
74✔
133
              });
134

135
    return extremal_points;
2✔
136
}
3✔
137

138
std::vector<Point2D<uint32_t>> generate_ellipse_pixels(float center_x, float center_y, float radius_x, float radius_y) {
7✔
139
    std::vector<Point2D<uint32_t>> ellipse_pixels;
7✔
140

141
    // Round center coordinates for calculation
142
    int rounded_center_x = static_cast<int>(std::round(center_x));
7✔
143
    int rounded_center_y = static_cast<int>(std::round(center_y));
7✔
144

145
    // Generate all pixels within the elliptical region (circle when radius_x == radius_y)
146
    int max_radius = static_cast<int>(std::max(radius_x, radius_y)) + 1;
7✔
147
    for (int dx = -max_radius; dx <= max_radius; ++dx) {
46✔
148
        for (int dy = -max_radius; dy <= max_radius; ++dy) {
286✔
149
            // Check if point is within elliptical radius using ellipse equation
150
            // (dx/radius_x)^2 + (dy/radius_y)^2 <= 1
151
            float normalized_dx = static_cast<float>(dx) / radius_x;
247✔
152
            float normalized_dy = static_cast<float>(dy) / radius_y;
247✔
153
            float ellipse_distance = normalized_dx * normalized_dx + normalized_dy * normalized_dy;
247✔
154

155
            if (ellipse_distance <= 1.0f) {
247✔
156
                int x = rounded_center_x + dx;
50✔
157
                int y = rounded_center_y + dy;
50✔
158

159
                // Only add pixels that are within valid bounds (non-negative)
160
                if (x >= 0 && y >= 0) {
50✔
161
                    ellipse_pixels.push_back({static_cast<uint32_t>(x), static_cast<uint32_t>(y)});
43✔
162
                }
163
            }
164
        }
165
    }
166

167
    return ellipse_pixels;
7✔
168
}
×
169

170
Mask2D combine_masks(Mask2D const & mask1, Mask2D const & mask2) {
5✔
171
    // Use a set to efficiently track unique pixel coordinates
172
    std::set<std::pair<uint32_t, uint32_t>> unique_pixels;
5✔
173
    Mask2D combined_mask;
5✔
174

175
    // Add all pixels from mask1
176
    for (auto const & point: mask1) {
15✔
177
        std::pair<uint32_t, uint32_t> pixel_key = {point.x, point.y};
10✔
178

179
        if (unique_pixels.find(pixel_key) == unique_pixels.end()) {
10✔
180
            unique_pixels.insert(pixel_key);
10✔
181
            combined_mask.push_back(point);
10✔
182
        }
183
    }
184

185
    // Add pixels from mask2 that aren't already present
186
    for (auto const & point: mask2) {
15✔
187
        std::pair<uint32_t, uint32_t> pixel_key = {point.x, point.y};
10✔
188

189
        if (unique_pixels.find(pixel_key) == unique_pixels.end()) {
10✔
190
            unique_pixels.insert(pixel_key);
5✔
191
            combined_mask.push_back(point);
5✔
192
        }
193
    }
194

195
    return combined_mask;
5✔
196
}
5✔
197

198
Mask2D subtract_masks(Mask2D const & mask1, Mask2D const & mask2) {
6✔
199
    // Create a set of pixels in mask2 for efficient lookup
200
    std::set<std::pair<uint32_t, uint32_t>> mask2_pixels;
6✔
201
    for (auto const & point: mask2) {
19✔
202
        mask2_pixels.insert({point.x, point.y});
13✔
203
    }
204

205
    // Keep only pixels from mask1 that are NOT in mask2
206
    Mask2D result_mask;
6✔
207
    for (auto const & point: mask1) {
20✔
208
        std::pair<uint32_t, uint32_t> pixel_key = {point.x, point.y};
14✔
209

210
        if (mask2_pixels.find(pixel_key) == mask2_pixels.end()) {
14✔
211
            result_mask.push_back(point);
7✔
212
        }
213
    }
214

215
    return result_mask;
6✔
216
}
6✔
217

218
Mask2D generate_outline_mask(Mask2D const & mask, int thickness, uint32_t image_width, uint32_t image_height) {
8✔
219
    if (mask.empty() || thickness <= 0) {
8✔
220
        return {};
2✔
221
    }
222

223
    // Create a set for fast lookup of mask pixels
224
    std::set<std::pair<uint32_t, uint32_t>> mask_pixels;
6✔
225
    for (auto const & point: mask) {
32✔
226
        mask_pixels.insert({point.x, point.y});
26✔
227
    }
228

229
    // Find outline pixels
230
    Mask2D outline_mask;
6✔
231
    std::set<std::pair<uint32_t, uint32_t>> outline_pixels;
6✔
232

233
    // For each pixel in the original mask, check if it's on the edge
234
    for (auto const & point: mask) {
32✔
235
        bool is_edge = false;
26✔
236

237
        // Check all 8 neighbors (including diagonals)
238
        for (int dx = -thickness; dx <= thickness; ++dx) {
33✔
239
            for (int dy = -thickness; dy <= thickness; ++dy) {
65✔
240
                if (dx == 0 && dy == 0) continue;// Skip the center pixel itself
58✔
241

242
                int32_t neighbor_x = static_cast<int32_t>(point.x) + dx;
55✔
243
                int32_t neighbor_y = static_cast<int32_t>(point.y) + dy;
55✔
244

245
                // Check bounds
246
                if (neighbor_x < 0 || neighbor_y < 0) {
55✔
247
                    is_edge = true;
3✔
248
                    break;
3✔
249
                }
250

251
                if (image_width != UINT32_MAX && static_cast<uint32_t>(neighbor_x) >= image_width) {
52✔
252
                    is_edge = true;
×
253
                    break;
×
254
                }
255

256
                if (image_height != UINT32_MAX && static_cast<uint32_t>(neighbor_y) >= image_height) {
52✔
257
                    is_edge = true;
×
258
                    break;
×
259
                }
260

261
                // Check if neighbor is not in the mask
262
                if (mask_pixels.find({static_cast<uint32_t>(neighbor_x), static_cast<uint32_t>(neighbor_y)}) == mask_pixels.end()) {
52✔
263
                    is_edge = true;
22✔
264
                    break;
22✔
265
                }
266
            }
267
            if (is_edge) break;
32✔
268
        }
269

270
        // If this pixel is on the edge, add it to outline
271
        if (is_edge) {
26✔
272
            outline_pixels.insert({point.x, point.y});
25✔
273
        }
274
    }
275

276
    // Convert set to vector
277
    outline_mask.reserve(outline_pixels.size());
6✔
278
    for (auto const & pixel: outline_pixels) {
31✔
279
        outline_mask.push_back({pixel.first, pixel.second});
25✔
280
    }
281

282
    return outline_mask;
6✔
283
}
6✔
284

285
std::vector<Point2D<uint32_t>> extract_line_pixels(
×
286
        std::vector<uint8_t> const & binary_img,
287
        ImageSize const image_size) {
288

289
    auto const height = image_size.height;
×
290
    auto const width = image_size.width;
×
291

292
    // Extract coordinates of the line pixels
293
    std::vector<Point2D<uint32_t>> line_pixels;
×
294
    line_pixels.reserve(width * height / 10);// Reserve space to avoid reallocations
×
295

NEW
296
    for (size_t row = 0; row < static_cast<size_t>(height); ++row) {
×
NEW
297
        for (size_t col = 0; col < static_cast<size_t>(width); ++col) {
×
NEW
298
            if (binary_img[row * static_cast<size_t>(width) + col] > 0) {
×
UNCOV
299
                line_pixels.push_back({static_cast<uint32_t>(col), static_cast<uint32_t>(row)});
×
300
            }
301
        }
302
    }
303

304
    return line_pixels;
×
305
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc