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

paulmthompson / WhiskerToolbox / 15737483263

18 Jun 2025 03:45PM UTC coverage: 68.069% (+0.6%) from 67.505%
15737483263

push

github

paulmthompson
bump to version 0.3.2

9124 of 13404 relevant lines covered (68.07%)

1157.73 hits per line

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

98.26
/src/WhiskerToolbox/DataManager/Masks/masks.test.cpp
1
#include "masks.hpp"
2
#include <catch2/catch_test_macros.hpp>
3
#include <cmath>
4

5
/**
6
 * @brief Tests for mask utility functions
7
 */
8

9
TEST_CASE("Mask utility functions", "[masks][utilities]") {
3✔
10

11
    SECTION("get_bounding_box basic functionality") {
3✔
12
        // Create a simple rectangular mask
13
        Mask2D mask = {
1✔
14
                {1, 1},
15
                {3, 1},
16
                {3, 4},
17
                {1, 4}};
3✔
18

19
        auto bounding_box = get_bounding_box(mask);
1✔
20
        Point2D<uint32_t> min_point = bounding_box.first;
1✔
21
        Point2D<uint32_t> max_point = bounding_box.second;
1✔
22

23
        REQUIRE(min_point.x == 1);
1✔
24
        REQUIRE(min_point.y == 1);
1✔
25
        REQUIRE(max_point.x == 3);
1✔
26
        REQUIRE(max_point.y == 4);
1✔
27
    }
4✔
28

29
    SECTION("get_bounding_box with single point") {
3✔
30
        Mask2D mask = {{5, 7}};
3✔
31

32
        auto bounding_box = get_bounding_box(mask);
1✔
33
        Point2D<uint32_t> min_point = bounding_box.first;
1✔
34
        Point2D<uint32_t> max_point = bounding_box.second;
1✔
35

36
        REQUIRE(min_point.x == 5);
1✔
37
        REQUIRE(min_point.y == 7);
1✔
38
        REQUIRE(max_point.x == 5);
1✔
39
        REQUIRE(max_point.y == 7);
1✔
40
    }
4✔
41

42
    SECTION("get_bounding_box with irregular mask") {
3✔
43
        // Create an irregular shaped mask (note: uint32_t can't be negative, so we use larger values)
44
        Mask2D mask = {
1✔
45
                {2, 2},
46
                {5, 1},
47
                {3, 8},
48
                {0, 4}};
3✔
49

50
        auto bounding_box = get_bounding_box(mask);
1✔
51
        Point2D<uint32_t> min_point = bounding_box.first;
1✔
52
        Point2D<uint32_t> max_point = bounding_box.second;
1✔
53

54
        REQUIRE(min_point.x == 0);
1✔
55
        REQUIRE(min_point.y == 1);
1✔
56
        REQUIRE(max_point.x == 5);
1✔
57
        REQUIRE(max_point.y == 8);
1✔
58
    }
4✔
59
}
3✔
60

61
TEST_CASE("create_mask utility function", "[masks][create]") {
4✔
62

63
    SECTION("create_mask from vectors") {
4✔
64
        std::vector<float> x = {1.0f, 2.0f, 3.0f};
3✔
65
        std::vector<float> y = {4.0f, 5.0f, 6.0f};
3✔
66

67
        auto mask = create_mask(x, y);
1✔
68

69
        REQUIRE(mask.size() == 3);
1✔
70
        REQUIRE(mask[0].x == 1);
1✔
71
        REQUIRE(mask[0].y == 4);
1✔
72
        REQUIRE(mask[1].x == 2);
1✔
73
        REQUIRE(mask[1].y == 5);
1✔
74
        REQUIRE(mask[2].x == 3);
1✔
75
        REQUIRE(mask[2].y == 6);
1✔
76
    }
5✔
77

78
    SECTION("create_mask with move semantics") {
4✔
79
        std::vector<float> x = {10.0f, 20.0f};
3✔
80
        std::vector<float> y = {30.0f, 40.0f};
3✔
81

82
        auto mask = create_mask(std::move(x), std::move(y));
1✔
83

84
        REQUIRE(mask.size() == 2);
1✔
85
        REQUIRE(mask[0].x == 10);
1✔
86
        REQUIRE(mask[0].y == 30);
1✔
87
        REQUIRE(mask[1].x == 20);
1✔
88
        REQUIRE(mask[1].y == 40);
1✔
89
    }
5✔
90

91
    SECTION("create_mask with rounding") {
4✔
92
        std::vector<float> x = {1.4f, 2.6f, 3.1f};
3✔
93
        std::vector<float> y = {4.7f, 5.2f, 6.9f};
3✔
94

95
        auto mask = create_mask(x, y);
1✔
96

97
        REQUIRE(mask.size() == 3);
1✔
98
        REQUIRE(mask[0].x == 1);  // 1.4 rounds to 1
1✔
99
        REQUIRE(mask[0].y == 5);  // 4.7 rounds to 5
1✔
100
        REQUIRE(mask[1].x == 3);  // 2.6 rounds to 3
1✔
101
        REQUIRE(mask[1].y == 5);  // 5.2 rounds to 5
1✔
102
        REQUIRE(mask[2].x == 3);  // 3.1 rounds to 3
1✔
103
        REQUIRE(mask[2].y == 7);  // 6.9 rounds to 7
1✔
104
    }
5✔
105

106
    SECTION("create_mask with negative values") {
4✔
107
        std::vector<float> x = {-1.0f, 2.0f, 3.0f};
3✔
108
        std::vector<float> y = {4.0f, -5.0f, 6.0f};
3✔
109

110
        auto mask = create_mask(x, y);
1✔
111

112
        REQUIRE(mask.size() == 3);
1✔
113
        REQUIRE(mask[0].x == 0);  // -1.0 clamped to 0
1✔
114
        REQUIRE(mask[0].y == 4);
1✔
115
        REQUIRE(mask[1].x == 2);
1✔
116
        REQUIRE(mask[1].y == 0);  // -5.0 clamped to 0
1✔
117
        REQUIRE(mask[2].x == 3);
1✔
118
        REQUIRE(mask[2].y == 6);
1✔
119
    }
5✔
120
}
4✔
121

122
TEST_CASE("get_mask_outline function", "[masks][outline]") {
5✔
123
    SECTION("Empty mask returns empty outline") {
5✔
124
        Mask2D empty_mask;
1✔
125
        auto outline = get_mask_outline(empty_mask);
1✔
126
        REQUIRE(outline.empty());
1✔
127
    }
6✔
128

129
    SECTION("Single point mask returns empty outline") {
5✔
130
        Mask2D single_point_mask = {{5, 5}};
3✔
131
        auto outline = get_mask_outline(single_point_mask);
1✔
132
        REQUIRE(outline.empty());
1✔
133
    }
6✔
134

135
    SECTION("Two point mask returns both points") {
5✔
136
        Mask2D two_point_mask = {{1, 1}, {3, 3}};
3✔
137
        auto outline = get_mask_outline(two_point_mask);
1✔
138
        REQUIRE(outline.size() == 2);
1✔
139

140
        // Should contain both points
141
        bool found_1_1 = false, found_3_3 = false;
1✔
142
        for (auto const & point: outline) {
3✔
143
            if (point.x == 1 && point.y == 1) found_1_1 = true;
2✔
144
            if (point.x == 3 && point.y == 3) found_3_3 = true;
2✔
145
        }
146
        REQUIRE(found_1_1);
1✔
147
        REQUIRE(found_3_3);
1✔
148
    }
6✔
149

150
    SECTION("Rectangular mask outline") {
5✔
151
        // Create a filled rectangle mask
152
        Mask2D rect_mask;
1✔
153
        for (uint32_t x = 1; x <= 5; x += 1) {
6✔
154
            for (uint32_t y = 2; y <= 4; y += 1) {
20✔
155
                rect_mask.push_back({x, y});
15✔
156
            }
157
        }
158

159
        auto outline = get_mask_outline(rect_mask);
1✔
160

161
        // Should contain at least the extremal points
162
        REQUIRE(outline.size() >= 4);
1✔
163

164
        // Check that extremal points are present
165
        bool found_max_x_min_y = false;// (5, 2)
1✔
166
        bool found_max_x_max_y = false;// (5, 4)
1✔
167
        bool found_min_x_min_y = false;// (1, 2)
1✔
168
        bool found_min_x_max_y = false;// (1, 4)
1✔
169

170
        for (auto const & point: outline) {
13✔
171
            if (point.x == 5 && point.y == 2) found_max_x_min_y = true;
12✔
172
            if (point.x == 5 && point.y == 4) found_max_x_max_y = true;
12✔
173
            if (point.x == 1 && point.y == 2) found_min_x_min_y = true;
12✔
174
            if (point.x == 1 && point.y == 4) found_min_x_max_y = true;
12✔
175
        }
176

177
        REQUIRE(found_max_x_min_y);
1✔
178
        REQUIRE(found_max_x_max_y);
1✔
179
        REQUIRE(found_min_x_min_y);
1✔
180
        REQUIRE(found_min_x_max_y);
1✔
181
    }
6✔
182

183
    SECTION("L-shaped mask outline") {
5✔
184
        // Create an L-shaped mask:
185
        // (1,3) (2,3)
186
        // (1,2)
187
        // (1,1) (2,1) (3,1)
188
        Mask2D l_mask = {
1✔
189
                {1, 3},
190
                {2, 3},
191
                {1, 2},
192
                {1, 1},
193
                {2, 1},
194
                {3, 1}};
3✔
195

196
        auto outline = get_mask_outline(l_mask);
1✔
197

198
        // Should find extremal points
199
        REQUIRE(outline.size() >= 3);
1✔
200

201
        // Should contain key extremal points like:
202
        // - max x for y=1: (3,1)
203
        // - max y for x=1: (1,3)
204
        // - max y for x=2: (2,3)
205

206
        bool found_3_1 = false;
1✔
207
        bool found_1_3 = false;
1✔
208
        bool found_2_3 = false;
1✔
209

210
        for (auto const & point: outline) {
7✔
211
            if (point.x == 3 && point.y == 1) found_3_1 = true;
6✔
212
            if (point.x == 1 && point.y == 3) found_1_3 = true;
6✔
213
            if (point.x == 2 && point.y == 3) found_2_3 = true;
6✔
214
        }
215

216
        REQUIRE(found_3_1);
1✔
217
        REQUIRE(found_1_3);
1✔
218
        REQUIRE(found_2_3);
1✔
219
    }
6✔
220
}
5✔
221

222
TEST_CASE("generate_ellipse_pixels function", "[masks][ellipse]") {
7✔
223

224
    SECTION("Perfect circle with radius 1") {
7✔
225
        auto pixels = generate_ellipse_pixels(5.0f, 5.0f, 1.0f, 1.0f);
1✔
226

227
        // Should contain the center and 4 cardinal points
228
        REQUIRE(pixels.size() >= 5);
1✔
229

230
        // Check that center point is included
231
        bool found_center = false;
1✔
232
        for (auto const & pixel: pixels) {
3✔
233
            if (pixel.x == 5 && pixel.y == 5) {
3✔
234
                found_center = true;
1✔
235
                break;
1✔
236
            }
237
        }
238
        REQUIRE(found_center);
1✔
239

240
        // All pixels should be within distance 1 from center
241
        for (auto const & pixel: pixels) {
6✔
242
            float dx = static_cast<float>(pixel.x) - 5.0f;
5✔
243
            float dy = static_cast<float>(pixel.y) - 5.0f;
5✔
244
            float distance = std::sqrt(dx * dx + dy * dy);
5✔
245
            REQUIRE(distance <= 1.01f);// Small tolerance for floating point
5✔
246
        }
247
    }
8✔
248

249
    SECTION("Perfect circle with radius 2") {
7✔
250
        auto pixels = generate_ellipse_pixels(10.0f, 10.0f, 2.0f, 2.0f);
1✔
251

252
        // Should have more pixels than radius 1 circle
253
        REQUIRE(pixels.size() > 5);
1✔
254

255
        // All pixels should be within distance 2 from center
256
        for (auto const & pixel: pixels) {
14✔
257
            float dx = static_cast<float>(pixel.x) - 10.0f;
13✔
258
            float dy = static_cast<float>(pixel.y) - 10.0f;
13✔
259
            float distance = std::sqrt(dx * dx + dy * dy);
13✔
260
            REQUIRE(distance <= 2.01f);// Small tolerance for floating point
13✔
261
        }
262

263
        // Should include pixels at approximately (8,10), (12,10), (10,8), (10,12)
264
        bool found_left = false, found_right = false, found_top = false, found_bottom = false;
1✔
265
        for (auto const & pixel: pixels) {
14✔
266
            if (pixel.x == 8 && pixel.y == 10) found_left = true;
13✔
267
            if (pixel.x == 12 && pixel.y == 10) found_right = true;
13✔
268
            if (pixel.x == 10 && pixel.y == 8) found_top = true;
13✔
269
            if (pixel.x == 10 && pixel.y == 12) found_bottom = true;
13✔
270
        }
271
        REQUIRE(found_left);
1✔
272
        REQUIRE(found_right);
1✔
273
        REQUIRE(found_top);
1✔
274
        REQUIRE(found_bottom);
1✔
275
    }
8✔
276

277
    SECTION("Ellipse with different X and Y radii") {
7✔
278
        auto pixels = generate_ellipse_pixels(5.0f, 5.0f, 3.0f, 1.0f);
1✔
279

280
        // Should have pixels stretched more in X direction
281
        REQUIRE(pixels.size() > 5);
1✔
282

283
        // All pixels should satisfy ellipse equation: (dx/3)² + (dy/1)² ≤ 1
284
        for (auto const & pixel: pixels) {
10✔
285
            float dx = static_cast<float>(pixel.x) - 5.0f;
9✔
286
            float dy = static_cast<float>(pixel.y) - 5.0f;
9✔
287
            float ellipse_value = (dx / 3.0f) * (dx / 3.0f) + (dy / 1.0f) * (dy / 1.0f);
9✔
288
            REQUIRE(ellipse_value <= 1.01f);// Small tolerance
9✔
289
        }
290

291
        // Should include pixels farther in X direction than Y direction
292
        bool found_far_x = false, found_far_y = false;
1✔
293
        for (auto const & pixel: pixels) {
10✔
294
            if (pixel.x == 8 && pixel.y == 5) found_far_x = true;
9✔
295
            if (pixel.x == 5 && pixel.y == 4) found_far_y = true;
9✔
296
        }
297
        REQUIRE(found_far_x);// Should reach to x=8 (3 units away)
1✔
298
        REQUIRE(found_far_y);// Should reach to y=4 (1 unit away)
1✔
299
    }
8✔
300

301
    SECTION("Ellipse at origin") {
7✔
302
        auto pixels = generate_ellipse_pixels(0.0f, 0.0f, 1.5f, 1.5f);
1✔
303

304
        // Should include origin
305
        bool found_origin = false;
1✔
306
        for (auto const & pixel: pixels) {
1✔
307
            if (pixel.x == 0 && pixel.y == 0) {
1✔
308
                found_origin = true;
1✔
309
                break;
1✔
310
            }
311
        }
312
        REQUIRE(found_origin);
1✔
313

314
        // All pixels should have non-negative coordinates (bounds checking)
315
        for (auto const & pixel: pixels) {
5✔
316
            REQUIRE(pixel.x >= 0);
4✔
317
            REQUIRE(pixel.y >= 0);
4✔
318
        }
319
    }
8✔
320

321
    SECTION("Ellipse partially outside bounds") {
7✔
322
        // Center at (1, 1) with radius 2 - should clip negative coordinates
323
        auto pixels = generate_ellipse_pixels(1.0f, 1.0f, 2.0f, 2.0f);
1✔
324

325
        // All returned pixels should have non-negative coordinates
326
        for (auto const & pixel: pixels) {
12✔
327
            REQUIRE(pixel.x >= 0);
11✔
328
            REQUIRE(pixel.y >= 0);
11✔
329
        }
330

331
        // Should still include the center
332
        bool found_center = false;
1✔
333
        for (auto const & pixel: pixels) {
5✔
334
            if (pixel.x == 1 && pixel.y == 1) {
5✔
335
                found_center = true;
1✔
336
                break;
1✔
337
            }
338
        }
339
        REQUIRE(found_center);
1✔
340

341
        // Should have fewer pixels than if it were fully in bounds
342
        REQUIRE(pixels.size() < 13);// Rough estimate for clipped circle
1✔
343
    }
8✔
344

345
    SECTION("Very small ellipse") {
7✔
346
        auto pixels = generate_ellipse_pixels(10.0f, 10.0f, 0.3f, 0.3f);
1✔
347

348
        // Should contain at least the center pixel
349
        REQUIRE(pixels.size() >= 1);
1✔
350

351
        bool found_center = false;
1✔
352
        for (auto const & pixel: pixels) {
1✔
353
            if (pixel.x == 10 && pixel.y == 10) {
1✔
354
                found_center = true;
1✔
355
                break;
1✔
356
            }
357
        }
358
        REQUIRE(found_center);
1✔
359
    }
8✔
360

361
    SECTION("Zero radius edge case") {
7✔
362
        // This tests behavior with zero radius - should return empty or just center
363
        auto pixels = generate_ellipse_pixels(5.0f, 5.0f, 0.0f, 0.0f);
1✔
364

365
        // Should either be empty or contain only center depending on implementation
366
        // The current implementation with divide by zero would be problematic,
367
        // so we expect this to either work correctly or be handled gracefully
368
        if (!pixels.empty()) {
1✔
369
            // If any pixels returned, center should be included
370
            bool found_center = false;
×
371
            for (auto const & pixel: pixels) {
×
372
                if (pixel.x == 5 && pixel.y == 5) {
×
373
                    found_center = true;
×
374
                    break;
×
375
                }
376
            }
377
            REQUIRE(found_center);
×
378
        }
379
    }
8✔
380
}
7✔
381

382
TEST_CASE("combine_masks function", "[masks][combination]") {
4✔
383

384
    SECTION("Combine two non-overlapping masks") {
4✔
385
        Mask2D mask1 = {{1, 1}, {2, 2}};
3✔
386
        Mask2D mask2 = {{3, 3}, {4, 4}};
3✔
387

388
        auto combined = combine_masks(mask1, mask2);
1✔
389

390
        REQUIRE(combined.size() == 4);
1✔
391

392
        // Should contain all original points
393
        bool found_1_1 = false, found_2_2 = false, found_3_3 = false, found_4_4 = false;
1✔
394
        for (auto const & point : combined) {
5✔
395
            if (point.x == 1 && point.y == 1) found_1_1 = true;
4✔
396
            if (point.x == 2 && point.y == 2) found_2_2 = true;
4✔
397
            if (point.x == 3 && point.y == 3) found_3_3 = true;
4✔
398
            if (point.x == 4 && point.y == 4) found_4_4 = true;
4✔
399
        }
400
        REQUIRE(found_1_1);
1✔
401
        REQUIRE(found_2_2);
1✔
402
        REQUIRE(found_3_3);
1✔
403
        REQUIRE(found_4_4);
1✔
404
    }
5✔
405

406
    SECTION("Combine masks with exact duplicates") {
4✔
407
        Mask2D mask1 = {{1, 1}, {2, 2}, {3, 3}};
3✔
408
        Mask2D mask2 = {{2, 2}, {3, 3}, {4, 4}};
3✔
409

410
        auto combined = combine_masks(mask1, mask2);
1✔
411

412
        // Should have 4 unique pixels: (1,1), (2,2), (3,3), (4,4)
413
        REQUIRE(combined.size() == 4);
1✔
414

415
        bool found_1_1 = false, found_2_2 = false, found_3_3 = false, found_4_4 = false;
1✔
416
        for (auto const & point : combined) {
5✔
417
            if (point.x == 1 && point.y == 1) found_1_1 = true;
4✔
418
            if (point.x == 2 && point.y == 2) found_2_2 = true;
4✔
419
            if (point.x == 3 && point.y == 3) found_3_3 = true;
4✔
420
            if (point.x == 4 && point.y == 4) found_4_4 = true;
4✔
421
        }
422
        REQUIRE(found_1_1);
1✔
423
        REQUIRE(found_2_2);
1✔
424
        REQUIRE(found_3_3);
1✔
425
        REQUIRE(found_4_4);
1✔
426
    }
5✔
427

428
    SECTION("Combine with empty masks") {
4✔
429
        Mask2D mask1 = {{1, 1}, {2, 2}};
3✔
430
        Mask2D empty_mask = {};
1✔
431

432
        auto combined1 = combine_masks(mask1, empty_mask);
1✔
433
        auto combined2 = combine_masks(empty_mask, mask1);
1✔
434

435
        REQUIRE(combined1.size() == 2);
1✔
436
        REQUIRE(combined2.size() == 2);
1✔
437
    }
5✔
438

439
    SECTION("Combine identical masks") {
4✔
440
        Mask2D mask = {{1, 1}, {2, 2}, {3, 3}};
3✔
441

442
        auto combined = combine_masks(mask, mask);
1✔
443

444
        // Should have same size as original (no duplicates)
445
        REQUIRE(combined.size() == 3);
1✔
446
    }
5✔
447
}
4✔
448

449
TEST_CASE("subtract_masks function", "[masks][subtraction]") {
6✔
450

451
    SECTION("Subtract non-overlapping masks") {
6✔
452
        Mask2D mask1 = {{1, 1}, {2, 2}, {3, 3}};
3✔
453
        Mask2D mask2 = {{4, 4}, {5, 5}};
3✔
454

455
        auto result = subtract_masks(mask1, mask2);
1✔
456

457
        // Should keep all of mask1 since no overlap
458
        REQUIRE(result.size() == 3);
1✔
459

460
        bool found_1_1 = false, found_2_2 = false, found_3_3 = false;
1✔
461
        for (auto const & point : result) {
4✔
462
            if (point.x == 1 && point.y == 1) found_1_1 = true;
3✔
463
            if (point.x == 2 && point.y == 2) found_2_2 = true;
3✔
464
            if (point.x == 3 && point.y == 3) found_3_3 = true;
3✔
465
        }
466
        REQUIRE(found_1_1);
1✔
467
        REQUIRE(found_2_2);
1✔
468
        REQUIRE(found_3_3);
1✔
469
    }
7✔
470

471
    SECTION("Subtract overlapping masks") {
6✔
472
        Mask2D mask1 = {{1, 1}, {2, 2}, {3, 3}, {4, 4}};
3✔
473
        Mask2D mask2 = {{2, 2}, {4, 4}};
3✔
474

475
        auto result = subtract_masks(mask1, mask2);
1✔
476

477
        // Should keep only (1,1) and (3,3)
478
        REQUIRE(result.size() == 2);
1✔
479

480
        bool found_1_1 = false, found_3_3 = false;
1✔
481
        for (auto const & point : result) {
3✔
482
            if (point.x == 1 && point.y == 1) found_1_1 = true;
2✔
483
            if (point.x == 3 && point.y == 3) found_3_3 = true;
2✔
484
            // Should not find (2,2) or (4,4)
485
            REQUIRE_FALSE(point.x == 2);
2✔
486
            REQUIRE_FALSE(point.y == 2);
2✔
487
            REQUIRE_FALSE(point.x == 4);
2✔
488
            REQUIRE_FALSE(point.y == 4);
2✔
489
        }
490
        REQUIRE(found_1_1);
1✔
491
        REQUIRE(found_3_3);
1✔
492
    }
7✔
493

494
    SECTION("Subtract empty mask") {
6✔
495
        Mask2D mask1 = {{1, 1}, {2, 2}};
3✔
496
        Mask2D empty_mask = {};
1✔
497

498
        auto result = subtract_masks(mask1, empty_mask);
1✔
499

500
        // Should keep all of mask1
501
        REQUIRE(result.size() == 2);
1✔
502
        REQUIRE(result[0].x == 1);
1✔
503
        REQUIRE(result[0].y == 1);
1✔
504
        REQUIRE(result[1].x == 2);
1✔
505
        REQUIRE(result[1].y == 2);
1✔
506
    }
7✔
507

508
    SECTION("Subtract from empty mask") {
6✔
509
        Mask2D empty_mask = {};
1✔
510
        Mask2D mask2 = {{1, 1}, {2, 2}};
3✔
511

512
        auto result = subtract_masks(empty_mask, mask2);
1✔
513

514
        // Should return empty mask
515
        REQUIRE(result.empty());
1✔
516
    }
7✔
517

518
    SECTION("Subtract identical masks") {
6✔
519
        Mask2D mask = {{1, 1}, {2, 2}, {3, 3}};
3✔
520

521
        auto result = subtract_masks(mask, mask);
1✔
522

523
        // Should return empty mask
524
        REQUIRE(result.empty());
1✔
525
    }
7✔
526

527
    SECTION("Subtract superset from subset") {
6✔
528
        Mask2D mask1 = {{2, 2}, {3, 3}};
3✔
529
        Mask2D mask2 = {{1, 1}, {2, 2}, {3, 3}, {4, 4}};
3✔
530

531
        auto result = subtract_masks(mask1, mask2);
1✔
532

533
        // Should return empty mask since all of mask1 is in mask2
534
        REQUIRE(result.empty());
1✔
535
    }
7✔
536
}
6✔
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