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

paulmthompson / WhiskerToolbox / 16024381988

02 Jul 2025 11:53AM UTC coverage: 72.467% (+0.7%) from 71.74%
16024381988

push

github

paulmthompson
change getting times to range access in mask data

6 of 6 new or added lines in 2 files covered. (100.0%)

102 existing lines in 11 files now uncovered.

11794 of 16275 relevant lines covered (72.47%)

1101.18 hits per line

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

98.72
/src/WhiskerToolbox/DataManager/Points/Point_Data.test.cpp
1
#include "Points/Point_Data.hpp"
2
#include "DigitalTimeSeries/interval_data.hpp"
3
#include <catch2/catch_test_macros.hpp>
4
#include <catch2/catch_approx.hpp>
5

6
TEST_CASE("PointData - Core functionality", "[points][data][core]") {
17✔
7
    PointData point_data;
17✔
8

9
    // Setup some test data
10
    Point2D<float> p1{1.0f, 2.0f};
17✔
11
    Point2D<float> p2{3.0f, 4.0f};
17✔
12
    Point2D<float> p3{5.0f, 6.0f};
17✔
13
    std::vector<Point2D<float>> points = {p1, p2};
51✔
14
    std::vector<Point2D<float>> more_points = {p3};
51✔
15

16
    SECTION("Adding and retrieving points at time") {
17✔
17
        // Add a single point at time 10
18
        point_data.addPointAtTime(TimeFrameIndex(10), p1);
1✔
19

20
        auto points_at_10 = point_data.getPointsAtTime(TimeFrameIndex(10));
1✔
21
        REQUIRE(points_at_10.size() == 1);
1✔
22
        REQUIRE(points_at_10[0].x == Catch::Approx(1.0f));
1✔
23
        REQUIRE(points_at_10[0].y == Catch::Approx(2.0f));
1✔
24

25
        // Add another point at the same time
26
        point_data.addPointAtTime(TimeFrameIndex(10), p2);
1✔
27
        points_at_10 = point_data.getPointsAtTime(TimeFrameIndex(10));
1✔
28
        REQUIRE(points_at_10.size() == 2);
1✔
29
        REQUIRE(points_at_10[1].x == Catch::Approx(3.0f));
1✔
30
        REQUIRE(points_at_10[1].y == Catch::Approx(4.0f));
1✔
31

32
        // Add points at a different time
33
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
34
        auto points_at_20 = point_data.getPointsAtTime(TimeFrameIndex(20));
1✔
35
        REQUIRE(points_at_20.size() == 1);
1✔
36
        REQUIRE(points_at_20[0].x == Catch::Approx(5.0f));
1✔
37
        REQUIRE(points_at_20[0].y == Catch::Approx(6.0f));
1✔
38
    }
18✔
39

40
    SECTION("Overwriting points at time") {
17✔
41
        // Add initial points
42
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
43

44
        // Overwrite with a single point
45
        point_data.overwritePointAtTime(TimeFrameIndex(10), p3);
1✔
46
        auto points_at_10 = point_data.getPointsAtTime(TimeFrameIndex(10));
1✔
47
        REQUIRE(points_at_10.size() == 1);
1✔
48
        REQUIRE(points_at_10[0].x == Catch::Approx(5.0f));
1✔
49
        REQUIRE(points_at_10[0].y == Catch::Approx(6.0f));
1✔
50

51
        // Overwrite with multiple points
52
        point_data.overwritePointsAtTime(TimeFrameIndex(10), points);
1✔
53
        points_at_10 = point_data.getPointsAtTime(TimeFrameIndex(10));
1✔
54
        REQUIRE(points_at_10.size() == 2);
1✔
55
        REQUIRE(points_at_10[0].x == Catch::Approx(1.0f));
1✔
56
        REQUIRE(points_at_10[0].y == Catch::Approx(2.0f));
1✔
57
    }
18✔
58

59
    SECTION("Clearing points at time") {
17✔
60
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
61
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
62

63
        point_data.clearAtTime(TimeFrameIndex(10));
1✔
64

65
        auto points_at_10 = point_data.getPointsAtTime(TimeFrameIndex(10));
1✔
66
        auto points_at_20 = point_data.getPointsAtTime(TimeFrameIndex(20));
1✔
67

68
        REQUIRE(points_at_10.empty());
1✔
69
        REQUIRE(points_at_20.size() == 1);
1✔
70
    }
18✔
71

72
    SECTION("GetAllPointsAsRange functionality") {
17✔
73
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
74
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
75

76
        size_t count = 0;
1✔
77
        int64_t first_time = 0;
1✔
78
        int64_t second_time = 0;
1✔
79

80
        for (const auto& pair : point_data.GetAllPointsAsRange()) {
3✔
81
            if (count == 0) {
2✔
82
                first_time = pair.time.getValue();
1✔
83
                REQUIRE(pair.points.size() == 2);
1✔
84
            } else if (count == 1) {
1✔
85
                second_time = pair.time.getValue();
1✔
86
                REQUIRE(pair.points.size() == 1);
1✔
87
            }
88
            count++;
2✔
89
        }
90

91
        REQUIRE(count == 2);
1✔
92
        REQUIRE(first_time == 10);
1✔
93
        REQUIRE(second_time == 20);
1✔
94
    }
17✔
95

96
    SECTION("GetPointsInRange functionality") {
17✔
97
        // Setup data at multiple time points
98
        point_data.addPointsAtTime(TimeFrameIndex(5), points);       // 2 points
9✔
99
        point_data.addPointsAtTime(TimeFrameIndex(10), points);      // 2 points  
9✔
100
        point_data.addPointsAtTime(TimeFrameIndex(15), more_points); // 1 point
9✔
101
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points); // 1 point
9✔
102
        point_data.addPointsAtTime(TimeFrameIndex(25), points);      // 2 points
9✔
103

104
        SECTION("Range includes some data") {
9✔
105
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(20)};
1✔
106
            size_t count = 0;
1✔
107
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
4✔
108
                if (count == 0) {
3✔
109
                    REQUIRE(pair.time.getValue() == 10);
1✔
110
                    REQUIRE(pair.points.size() == 2);
1✔
111
                } else if (count == 1) {
2✔
112
                    REQUIRE(pair.time.getValue() == 15);
1✔
113
                    REQUIRE(pair.points.size() == 1);
1✔
114
                } else if (count == 2) {
1✔
115
                    REQUIRE(pair.time.getValue() == 20);
1✔
116
                    REQUIRE(pair.points.size() == 1);
1✔
117
                }
118
                count++;
3✔
119
            }
120
            REQUIRE(count == 3); // Should include times 10, 15, 20
1✔
121
        }
9✔
122

123
        SECTION("Range includes all data") {
9✔
124
            TimeFrameInterval interval{TimeFrameIndex(0), TimeFrameIndex(30)};
1✔
125
            size_t count = 0;
1✔
126
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
6✔
127
                count++;
5✔
128
            }
129
            REQUIRE(count == 5); // Should include all 5 time points
1✔
130
        }
9✔
131

132
        SECTION("Range includes no data") {
9✔
133
            TimeFrameInterval interval{TimeFrameIndex(100), TimeFrameIndex(200)};
1✔
134
            size_t count = 0;
1✔
135
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
1✔
UNCOV
136
                count++;
×
137
            }
138
            REQUIRE(count == 0); // Should be empty
1✔
139
        }
9✔
140

141
        SECTION("Range with single time point") {
9✔
142
            TimeFrameInterval interval{TimeFrameIndex(15), TimeFrameIndex(15)};
1✔
143
            size_t count = 0;
1✔
144
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
2✔
145
                REQUIRE(pair.time.getValue() == 15);
1✔
146
                REQUIRE(pair.points.size() == 1);
1✔
147
                count++;
1✔
148
            }
149
            REQUIRE(count == 1); // Should include only time 15
1✔
150
        }
9✔
151

152
        SECTION("Range with start > end") {
9✔
153
            TimeFrameInterval interval{TimeFrameIndex(20), TimeFrameIndex(10)};
1✔
154
            size_t count = 0;
1✔
155
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
1✔
UNCOV
156
                count++;
×
157
            }
158
            REQUIRE(count == 0); // Should be empty when start > end
1✔
159
        }
9✔
160

161
        SECTION("Range with timeframe conversion - same timeframes") {
9✔
162
            // Test with same source and target timeframes
163
            std::vector<int> times = {5, 10, 15, 20, 25};
3✔
164
            auto timeframe = std::make_shared<TimeFrame>(times);
1✔
165
            
166
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(20)};
1✔
167
            size_t count = 0;
1✔
168
            for (const auto& pair : point_data.GetPointsInRange(interval, timeframe, timeframe)) {
4✔
169
                if (count == 0) {
3✔
170
                    REQUIRE(pair.time.getValue() == 10);
1✔
171
                    REQUIRE(pair.points.size() == 2);
1✔
172
                } else if (count == 1) {
2✔
173
                    REQUIRE(pair.time.getValue() == 15);
1✔
174
                    REQUIRE(pair.points.size() == 1);
1✔
175
                } else if (count == 2) {
1✔
176
                    REQUIRE(pair.time.getValue() == 20);
1✔
177
                    REQUIRE(pair.points.size() == 1);
1✔
178
                }
179
                count++;
3✔
180
            }
181
            REQUIRE(count == 3); // Should include times 10, 15, 20
1✔
182
        }
10✔
183

184
        SECTION("Range with timeframe conversion - different timeframes") {
9✔
185
            // Create a separate point data instance for timeframe conversion test
186
            PointData timeframe_test_data;
1✔
187
            
188
            // Create source timeframe (video frames)
189
            std::vector<int> video_times = {0, 10, 20, 30, 40};  
3✔
190
            auto video_timeframe = std::make_shared<TimeFrame>(video_times);
1✔
191
            
192
            // Create target timeframe (data sampling)
193
            std::vector<int> data_times = {0, 5, 10, 15, 20, 25, 30, 35, 40}; 
3✔
194
            auto data_timeframe = std::make_shared<TimeFrame>(data_times);
1✔
195
            
196
            // Add data at target timeframe indices
197
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(2), points);  // At data timeframe index 2 (time=10)
1✔
198
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(3), more_points);  // At data timeframe index 3 (time=15)
1✔
199
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(4), points);  // At data timeframe index 4 (time=20)
1✔
200
            
201
            // Query video frames 1-2 (times 10-20) which should map to data indices 2-4 (times 10-20)
202
            TimeFrameInterval video_interval{TimeFrameIndex(1), TimeFrameIndex(2)};
1✔
203
            size_t count = 0;
1✔
204
            for (const auto& pair : timeframe_test_data.GetPointsInRange(video_interval, video_timeframe, data_timeframe)) {
4✔
205
                if (count == 0) {
3✔
206
                    REQUIRE(pair.time.getValue() == 2);
1✔
207
                    REQUIRE(pair.points.size() == 2);
1✔
208
                } else if (count == 1) {
2✔
209
                    REQUIRE(pair.time.getValue() == 3);
1✔
210
                    REQUIRE(pair.points.size() == 1);
1✔
211
                } else if (count == 2) {
1✔
212
                    REQUIRE(pair.time.getValue() == 4);
1✔
213
                    REQUIRE(pair.points.size() == 2);
1✔
214
                }
215
                count++;
3✔
216
            }
217
            REQUIRE(count == 3); // Should include converted times 2, 3, 4
1✔
218
        }
10✔
219

220
        SECTION("TimeFrameInterval overloads") {
9✔
221
            // Test the TimeFrameInterval convenience overload
222
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(20)};
1✔
223
            
224
            size_t count = 0;
1✔
225
            for (const auto& pair : point_data.GetPointsInRange(interval)) {
4✔
226
                if (count == 0) {
3✔
227
                    REQUIRE(pair.time.getValue() == 10);
1✔
228
                    REQUIRE(pair.points.size() == 2);
1✔
229
                } else if (count == 1) {
2✔
230
                    REQUIRE(pair.time.getValue() == 15);
1✔
231
                    REQUIRE(pair.points.size() == 1);
1✔
232
                } else if (count == 2) {
1✔
233
                    REQUIRE(pair.time.getValue() == 20);
1✔
234
                    REQUIRE(pair.points.size() == 1);
1✔
235
                }
236
                count++;
3✔
237
            }
238
            REQUIRE(count == 3); // Should include times 10, 15, 20
1✔
239
        }
9✔
240

241
        SECTION("TimeFrameInterval with timeframe conversion") {
9✔
242
            // Create a separate point data instance for timeframe conversion test
243
            PointData timeframe_test_data;
1✔
244
            
245
            // Create source timeframe (video frames)
246
            std::vector<int> video_times = {0, 10, 20, 30, 40};  
3✔
247
            auto video_timeframe = std::make_shared<TimeFrame>(video_times);
1✔
248
            
249
            // Create target timeframe (data sampling)
250
            std::vector<int> data_times = {0, 5, 10, 15, 20, 25, 30, 35, 40}; 
3✔
251
            auto data_timeframe = std::make_shared<TimeFrame>(data_times);
1✔
252
            
253
            // Add data at target timeframe indices
254
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(2), points);  // At data timeframe index 2 (time=10)
1✔
255
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(3), more_points);  // At data timeframe index 3 (time=15)
1✔
256
            timeframe_test_data.addPointsAtTime(TimeFrameIndex(4), points);  // At data timeframe index 4 (time=20)
1✔
257
            
258
            // Create interval in video timeframe (frames 1-2 covering times 10-20)
259
            TimeFrameInterval video_interval{TimeFrameIndex(1), TimeFrameIndex(2)};
1✔
260
            
261
            size_t count = 0;
1✔
262
            for (const auto& pair : timeframe_test_data.GetPointsInRange(video_interval, video_timeframe, data_timeframe)) {
4✔
263
                if (count == 0) {
3✔
264
                    REQUIRE(pair.time.getValue() == 2);
1✔
265
                    REQUIRE(pair.points.size() == 2);
1✔
266
                } else if (count == 1) {
2✔
267
                    REQUIRE(pair.time.getValue() == 3);
1✔
268
                    REQUIRE(pair.points.size() == 1);
1✔
269
                } else if (count == 2) {
1✔
270
                    REQUIRE(pair.time.getValue() == 4);
1✔
271
                    REQUIRE(pair.points.size() == 2);
1✔
272
                }
273
                count++;
3✔
274
            }
275
            REQUIRE(count == 3); // Should include converted times 2, 3, 4
1✔
276
        }
10✔
277
    }
17✔
278

279
    SECTION("Setting and getting image size") {
17✔
280
        ImageSize size{640, 480};
1✔
281
        point_data.setImageSize(size);
1✔
282

283
        auto retrieved_size = point_data.getImageSize();
1✔
284
        REQUIRE(retrieved_size.width == 640);
1✔
285
        REQUIRE(retrieved_size.height == 480);
1✔
286
    }
17✔
287

288
    SECTION("Overwriting points at multiple times") {
17✔
289
        std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20)};
3✔
290
        std::vector<std::vector<Point2D<float>>> points_vec = {points, more_points};
5✔
291

292
        point_data.overwritePointsAtTimes(times, points_vec);
1✔
293

294
        auto points_at_10 = point_data.getPointsAtTime(TimeFrameIndex(10));
1✔
295
        auto points_at_20 = point_data.getPointsAtTime(TimeFrameIndex(20));
1✔
296

297
        REQUIRE(points_at_10.size() == 2);
1✔
298
        REQUIRE(points_at_20.size() == 1);
1✔
299
    }
18✔
300

301
    SECTION("Getting times with points") {
17✔
302
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
303
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
304

305
        auto times = point_data.getTimesWithData();
1✔
306

307
        REQUIRE(times.size() == 2);
1✔
308
        
309
        // Convert to vector for easier testing or use iterators
310
        auto it = times.begin();
1✔
311
        REQUIRE(*it == TimeFrameIndex(10));
1✔
312
        ++it;
1✔
313
        REQUIRE(*it == TimeFrameIndex(20));
1✔
314
    }
17✔
315

316
    SECTION("Getting max points") {
17✔
317
        point_data.addPointsAtTime(TimeFrameIndex(10), points);      // 2 points
1✔
318
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points); // 1 point
1✔
319

320
        auto max_points = point_data.getMaxPoints();
1✔
321
        REQUIRE(max_points == 2);
1✔
322
    }
17✔
323
}
35✔
324

325
TEST_CASE("PointData - Edge cases and error handling", "[points][data][error]") {
7✔
326
    PointData point_data;
7✔
327

328
    SECTION("Getting points at non-existent time") {
7✔
329
        auto points = point_data.getPointsAtTime(TimeFrameIndex(999));
1✔
330
        REQUIRE(points.empty());
1✔
331
    }
8✔
332

333
    SECTION("Clearing points at non-existent time") {
7✔
334
        point_data.clearAtTime(TimeFrameIndex(42));
1✔
335
        auto points = point_data.getPointsAtTime(TimeFrameIndex(42));
1✔
336
        REQUIRE(points.empty());
1✔
337

338
        // Verify the time was NOT created
339
        bool found = false;
1✔
340
        for (const auto& pair : point_data.GetAllPointsAsRange()) {
1✔
UNCOV
341
            if (pair.time.getValue() == 42) {
×
UNCOV
342
                found = true;
×
UNCOV
343
                break;
×
344
            }
345
        }
346
        REQUIRE(found == false);
1✔
347
    }
8✔
348

349
    SECTION("Empty range with no data") {
7✔
350
        auto range = point_data.GetAllPointsAsRange();
1✔
351
        size_t count = 0;
1✔
352

353
        for (auto const& pair : range) {
1✔
UNCOV
354
            count++;
×
355
        }
356

357
        REQUIRE(count == 0);
1✔
358
    }
7✔
359

360
    SECTION("Adding empty points vector") {
7✔
361
        std::vector<Point2D<float>> empty_points;
1✔
362
        point_data.addPointsAtTime(TimeFrameIndex(10), empty_points);
1✔
363

364
        auto points = point_data.getPointsAtTime(TimeFrameIndex(10));
1✔
365
        REQUIRE(points.empty());
1✔
366

367
        // Verify the time was created
368
        bool found = false;
1✔
369
        for (const auto& pair : point_data.GetAllPointsAsRange()) {
1✔
370
            if (pair.time.getValue() == 10) {
1✔
371
                found = true;
1✔
372
                break;
1✔
373
            }
374
        }
375
        REQUIRE(found);
1✔
376
    }
8✔
377

378
    SECTION("Overwriting points at times with mismatched vectors") {
7✔
379
        std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20), TimeFrameIndex(30)};
3✔
380
        std::vector<std::vector<Point2D<float>>> points = {
1✔
381
                {{1.0f, 2.0f}},
382
                {{3.0f, 4.0f}}
383
        }; // Only 2 elements
6✔
384

385
        // This should not modify the data
386
        point_data.overwritePointsAtTimes(times, points);
1✔
387

388
        auto range = point_data.GetAllPointsAsRange();
1✔
389
        size_t count = 0;
1✔
390
        for (auto const& pair : range) {
1✔
UNCOV
391
            count++;
×
392
        }
393

394
        // No points should be added since we had a size mismatch
395
        REQUIRE(count == 0);
1✔
396
    }
8✔
397

398
    SECTION("Multiple operations sequence") {
7✔
399
        Point2D<float> p1{1.0f, 2.0f};
1✔
400

401
        // Add, clear, add again to test internal state consistency
402
        point_data.addPointAtTime(TimeFrameIndex(5), p1);
1✔
403
        point_data.clearAtTime(TimeFrameIndex(5));
1✔
404
        point_data.addPointAtTime(TimeFrameIndex(5), p1);
1✔
405

406
        auto points = point_data.getPointsAtTime(TimeFrameIndex(5));
1✔
407
        REQUIRE(points.size() == 1);
1✔
408
        REQUIRE(points[0].x == Catch::Approx(1.0f));
1✔
409
    }
8✔
410

411
    SECTION("Construction from map") {
7✔
412
        std::map<TimeFrameIndex, std::vector<Point2D<float>>> map_data = {
1✔
413
                {TimeFrameIndex(10), {{1.0f, 2.0f}, {3.0f, 4.0f}}},
2✔
414
                {TimeFrameIndex(20), {{5.0f, 6.0f}}}
2✔
415
        };
12✔
416

417
        PointData point_data_from_map(map_data);
1✔
418

419
        auto points_at_10 = point_data_from_map.getPointsAtTime(TimeFrameIndex(10));
1✔
420
        auto points_at_20 = point_data_from_map.getPointsAtTime(TimeFrameIndex(20));
1✔
421

422
        REQUIRE(points_at_10.size() == 2);
1✔
423
        REQUIRE(points_at_20.size() == 1);
1✔
424
        REQUIRE(points_at_10[0].x == Catch::Approx(1.0f));
1✔
425
        REQUIRE(points_at_20[0].x == Catch::Approx(5.0f));
1✔
426
    }
8✔
427
}
27✔
428

429
TEST_CASE("PointData - Copy and Move operations", "[points][data][copy][move]") {
17✔
430
    PointData source_data;
17✔
431
    PointData target_data;
17✔
432

433
    // Setup test data
434
    std::vector<Point2D<float>> points_10 = {{1.0f, 2.0f}, {3.0f, 4.0f}};
51✔
435
    std::vector<Point2D<float>> points_15 = {{5.0f, 6.0f}};
51✔
436
    std::vector<Point2D<float>> points_20 = {{7.0f, 8.0f}, {9.0f, 10.0f}, {11.0f, 12.0f}};
51✔
437
    std::vector<Point2D<float>> points_25 = {{13.0f, 14.0f}};
51✔
438

439
    source_data.addPointsAtTime(TimeFrameIndex(10), points_10);
17✔
440
    source_data.addPointsAtTime(TimeFrameIndex(15), points_15);
17✔
441
    source_data.addPointsAtTime(TimeFrameIndex(20), points_20);
17✔
442
    source_data.addPointsAtTime(TimeFrameIndex(25), points_25);
17✔
443

444
    SECTION("copyTo - time range operations") {
17✔
445
        SECTION("Copy entire range") {
5✔
446
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(25)};
1✔
447
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
448
            
449
            REQUIRE(copied == 7); // 2 + 1 + 3 + 1 = 7 points total
1✔
450
            
451
            // Verify all points were copied
452
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).size() == 2);
1✔
453
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(15)).size() == 1);
1✔
454
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
455
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(25)).size() == 1);
1✔
456
            
457
            // Verify source data is unchanged
458
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(10)).size() == 2);
1✔
459
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
460
        }
5✔
461

462
        SECTION("Copy partial range") {
5✔
463
            TimeFrameInterval interval{TimeFrameIndex(15), TimeFrameIndex(20)};
1✔
464
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
465
            
466
            REQUIRE(copied == 4); // 1 + 3 = 4 points
1✔
467
            
468
            // Verify only points in range were copied
469
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).empty());
1✔
470
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(15)).size() == 1);
1✔
471
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
472
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(25)).empty());
1✔
473
        }
5✔
474

475
        SECTION("Copy single time point") {
5✔
476
            TimeFrameInterval interval{TimeFrameIndex(20), TimeFrameIndex(20)};
1✔
477
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
478
            
479
            REQUIRE(copied == 3);
1✔
480
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
481
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).empty());
1✔
482
        }
5✔
483

484
        SECTION("Copy non-existent range") {
5✔
485
            TimeFrameInterval interval{TimeFrameIndex(100), TimeFrameIndex(200)};
1✔
486
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
487
            
488
            REQUIRE(copied == 0);
1✔
489
            REQUIRE(target_data.getTimesWithData().empty());
1✔
490
        }
5✔
491

492
        SECTION("Copy with invalid range (start > end)") {
5✔
493
            TimeFrameInterval interval{TimeFrameIndex(25), TimeFrameIndex(10)};
1✔
494
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
495
            
496
            REQUIRE(copied == 0);
1✔
497
            REQUIRE(target_data.getTimesWithData().empty());
1✔
498
        }
5✔
499
    }
17✔
500

501
    SECTION("copyTo - specific times vector operations") {
17✔
502
        SECTION("Copy multiple specific times") {
5✔
503
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20)};
3✔
504
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
505
            
506
            REQUIRE(copied == 5); // 2 + 3 = 5 points
1✔
507
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).size() == 2);
1✔
508
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(15)).empty());
1✔
509
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
510
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(25)).empty());
1✔
511
        }
6✔
512

513
        SECTION("Copy times in non-sequential order") {
5✔
514
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(25), TimeFrameIndex(10), TimeFrameIndex(15)}; // Non-sequential
3✔
515
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
516
            
517
            REQUIRE(copied == 4); // 1 + 2 + 1 = 4 points
1✔
518
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).size() == 2);
1✔
519
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(15)).size() == 1);
1✔
520
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(25)).size() == 1);
1✔
521
        }
6✔
522

523
        SECTION("Copy with duplicate times in vector") {
5✔
524
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(10), TimeFrameIndex(20)}; // Duplicate time
3✔
525
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
526
            
527
            // Should copy 10 twice and 20 once
528
            REQUIRE(copied == 7); // 2 + 2 + 3 = 7 points (10 copied twice)
1✔
529
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).size() == 4); // 2 + 2 = 4 (added twice)
1✔
530
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
531
        }
6✔
532

533
        SECTION("Copy non-existent times") {
5✔
534
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(100), TimeFrameIndex(200), TimeFrameIndex(300)};
3✔
535
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
536
            
537
            REQUIRE(copied == 0);
1✔
538
            REQUIRE(target_data.getTimesWithData().empty());
1✔
539
        }
6✔
540

541
        SECTION("Copy mixed existent and non-existent times") {
5✔
542
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(100), TimeFrameIndex(20), TimeFrameIndex(200)};
3✔
543
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
544
            
545
            REQUIRE(copied == 5); // Only times 10 and 20 exist
1✔
546
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).size() == 2);
1✔
547
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
548
        }
6✔
549
    }
17✔
550

551
    SECTION("moveTo - time range operations") {
17✔
552
        SECTION("Move entire range") {
3✔
553
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(25)};
1✔
554
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
555
            
556
            REQUIRE(moved == 7); // 2 + 1 + 3 + 1 = 7 points total
1✔
557
            
558
            // Verify all points were moved to target
559
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).size() == 2);
1✔
560
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(15)).size() == 1);
1✔
561
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
562
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(25)).size() == 1);
1✔
563
            
564
            // Verify source data is now empty
565
            REQUIRE(source_data.getTimesWithData().empty());
1✔
566
        }
3✔
567

568
        SECTION("Move partial range") {
3✔
569
            TimeFrameInterval interval{TimeFrameIndex(15), TimeFrameIndex(20)};
1✔
570
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
571
            
572
            REQUIRE(moved == 4); // 1 + 3 = 4 points
1✔
573
            
574
            // Verify only points in range were moved
575
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(15)).size() == 1);
1✔
576
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
577
            
578
            // Verify source still has data outside the range
579
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(10)).size() == 2);
1✔
580
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(25)).size() == 1);
1✔
581
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(15)).empty());
1✔
582
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(20)).empty());
1✔
583
        }
3✔
584

585
        SECTION("Move single time point") {
3✔
586
            TimeFrameInterval interval{TimeFrameIndex(20), TimeFrameIndex(20)};
1✔
587
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
588
            
589
            REQUIRE(moved == 3);
1✔
590
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
591
            
592
            // Verify only time 20 was removed from source
593
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(10)).size() == 2);
1✔
594
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(15)).size() == 1);
1✔
595
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(20)).empty());
1✔
596
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(25)).size() == 1);
1✔
597
        }
3✔
598
    }
17✔
599

600
    SECTION("moveTo - specific times vector operations") {
17✔
601
        SECTION("Move multiple specific times") {
2✔
602
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20)};
3✔
603
            std::size_t moved = source_data.moveTo(target_data, times);
1✔
604
            
605
            REQUIRE(moved == 5); // 2 + 3 = 5 points
1✔
606
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).size() == 2);
1✔
607
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
608
            
609
            // Verify moved times are cleared from source
610
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(10)).empty());
1✔
611
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(20)).empty());
1✔
612
            // But other times remain
613
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(15)).size() == 1);
1✔
614
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(25)).size() == 1);
1✔
615
        }
3✔
616

617
        SECTION("Move times in non-sequential order") {
2✔
618
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(25), TimeFrameIndex(10), TimeFrameIndex(15)}; // Non-sequential
3✔
619
            std::size_t moved = source_data.moveTo(target_data, times);
1✔
620
            
621
            REQUIRE(moved == 4); // 1 + 2 + 1 = 4 points
1✔
622
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).size() == 2);
1✔
623
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(15)).size() == 1);
1✔
624
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(25)).size() == 1);
1✔
625
            
626
            // Only time 20 should remain in source
627
            REQUIRE(source_data.getTimesWithData().size() == 1);
1✔
628
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(20)).size() == 3);
1✔
629
        }
3✔
630
    }
17✔
631

632
    SECTION("Copy/Move to target with existing data") {
17✔
633
        // Add some existing data to target
634
        std::vector<Point2D<float>> existing_points = {{100.0f, 200.0f}};
6✔
635
        target_data.addPointsAtTime(TimeFrameIndex(10), existing_points);
2✔
636

637
        SECTION("Copy to existing time adds points") {
2✔
638
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(10)};
1✔
639
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
640
            
641
            REQUIRE(copied == 2);
1✔
642
            // Should have existing point plus copied points
643
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).size() == 3); // 1 existing + 2 copied
1✔
644
        }
2✔
645

646
        SECTION("Move to existing time adds points") {
2✔
647
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(10)};
1✔
648
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
649
            
650
            REQUIRE(moved == 2);
1✔
651
            // Should have existing point plus moved points
652
            REQUIRE(target_data.getPointsAtTime(TimeFrameIndex(10)).size() == 3); // 1 existing + 2 moved
1✔
653
            
654
            // Source should no longer have points at time 10
655
            REQUIRE(source_data.getPointsAtTime(TimeFrameIndex(10)).empty());
1✔
656
        }
2✔
657
    }
19✔
658
}
34✔
659

660
TEST_CASE("PointData - Image scaling", "[points][data][scaling]") {
3✔
661
    PointData point_data;
3✔
662
    
663
    // Setup test data with known coordinates
664
    std::vector<Point2D<float>> points = {{100.0f, 200.0f}, {300.0f, 400.0f}};
9✔
665
    point_data.addPointsAtTime(TimeFrameIndex(10), points);
3✔
666
    
667
    SECTION("Scaling from known size") {
3✔
668
        // Set initial image size
669
        ImageSize initial_size{640, 480};
1✔
670
        point_data.setImageSize(initial_size);
1✔
671
        
672
        // Scale to larger size
673
        ImageSize new_size{1280, 960};
1✔
674
        point_data.changeImageSize(new_size);
1✔
675
        
676
        auto scaled_points = point_data.getPointsAtTime(TimeFrameIndex(10));
1✔
677
        
678
        // Points should be scaled by factor of 2
679
        REQUIRE(scaled_points[0].x == Catch::Approx(200.0f));
1✔
680
        REQUIRE(scaled_points[0].y == Catch::Approx(400.0f));
1✔
681
        REQUIRE(scaled_points[1].x == Catch::Approx(600.0f));
1✔
682
        REQUIRE(scaled_points[1].y == Catch::Approx(800.0f));
1✔
683
        
684
        // Image size should be updated
685
        auto current_size = point_data.getImageSize();
1✔
686
        REQUIRE(current_size.width == 1280);
1✔
687
        REQUIRE(current_size.height == 960);
1✔
688
    }
4✔
689
    
690
    SECTION("Scaling with no initial size set") {
3✔
691
        // Try to scale without setting initial size
692
        ImageSize new_size{1280, 960};
1✔
693
        point_data.changeImageSize(new_size);
1✔
694
        
695
        // Points should remain unchanged since no initial size was set
696
        auto unchanged_points = point_data.getPointsAtTime(TimeFrameIndex(10));
1✔
697
        REQUIRE(unchanged_points[0].x == Catch::Approx(100.0f));
1✔
698
        REQUIRE(unchanged_points[0].y == Catch::Approx(200.0f));
1✔
699
        
700
        // But image size should now be set
701
        auto current_size = point_data.getImageSize();
1✔
702
        REQUIRE(current_size.width == 1280);
1✔
703
        REQUIRE(current_size.height == 960);
1✔
704
    }
4✔
705
    
706
    SECTION("Scaling to same size does nothing") {
3✔
707
        ImageSize size{640, 480};
1✔
708
        point_data.setImageSize(size);
1✔
709
        
710
        // Scale to same size
711
        point_data.changeImageSize(size);
1✔
712
        
713
        // Points should remain unchanged
714
        auto unchanged_points = point_data.getPointsAtTime(TimeFrameIndex(10));
1✔
715
        REQUIRE(unchanged_points[0].x == Catch::Approx(100.0f));
1✔
716
        REQUIRE(unchanged_points[0].y == Catch::Approx(200.0f));
1✔
717
    }
4✔
718
}
6✔
719

720
TEST_CASE("PointData - Timeframe conversion", "[points][data][timeframe]") {
4✔
721
    PointData point_data;
4✔
722
    
723
    // Setup test data
724
    std::vector<Point2D<float>> points = {{100.0f, 200.0f}, {300.0f, 400.0f}};
12✔
725
    point_data.addPointsAtTime(TimeFrameIndex(10), points);
4✔
726
    point_data.addPointsAtTime(TimeFrameIndex(20), points);
4✔
727
    
728
    SECTION("Same timeframe returns original data") {
4✔
729
        // Create a single timeframe
730
        std::vector<int> times = {5, 10, 15, 20, 25};
3✔
731
        auto timeframe = std::make_shared<TimeFrame>(times);
1✔
732
        
733
        // Query with same source and target timeframe
734
        auto result = point_data.getPointsAtTime(TimeFrameIndex(10), timeframe, timeframe);
1✔
735
        
736
        REQUIRE(result.size() == 2);
1✔
737
        REQUIRE(result[0].x == Catch::Approx(100.0f));
1✔
738
        REQUIRE(result[0].y == Catch::Approx(200.0f));
1✔
739
    }
5✔
740
    
741
    SECTION("Different timeframes with conversion") {
4✔
742
        // Create source timeframe (e.g., video frames)
743
        std::vector<int> video_times = {0, 10, 20, 30, 40};  // Video at 1 Hz
3✔
744
        auto video_timeframe = std::make_shared<TimeFrame>(video_times);
1✔
745
        
746
        // Create target timeframe (e.g., data sampling at higher rate)
747
        std::vector<int> data_times = {0, 5, 10, 15, 20, 25, 30, 35, 40}; // Data at 2 Hz
3✔
748
        auto data_timeframe = std::make_shared<TimeFrame>(data_times);
1✔
749
        
750
        // Clear existing data and add data at the correct indices for the target timeframe
751
        PointData test_point_data;
1✔
752
        
753
        // Video frame 1 (time=10) should map to data_timeframe index 2 (time=10)
754
        // Video frame 2 (time=20) should map to data_timeframe index 4 (time=20)
755
        test_point_data.addPointsAtTime(TimeFrameIndex(2), points);  // At data timeframe index 2 (time=10)
1✔
756
        test_point_data.addPointsAtTime(TimeFrameIndex(4), points);  // At data timeframe index 4 (time=20)
1✔
757
        
758
        // Query video frame 1 (time=10) which should map to data index 2 (time=10)
759
        auto result = test_point_data.getPointsAtTime(TimeFrameIndex(1), video_timeframe, data_timeframe);
1✔
760
        
761
        REQUIRE(result.size() == 2);
1✔
762
        REQUIRE(result[0].x == Catch::Approx(100.0f));
1✔
763
        REQUIRE(result[0].y == Catch::Approx(200.0f));
1✔
764
    }
5✔
765
    
766
    SECTION("Timeframe conversion with no matching data") {
4✔
767
        // Create timeframes where conversion maps to non-existent data
768
        std::vector<int> video_times = {0, 5, 10};
3✔
769
        auto video_timeframe = std::make_shared<TimeFrame>(video_times);
1✔
770
        
771
        std::vector<int> data_times = {0, 3, 7, 15, 25};
3✔
772
        auto data_timeframe = std::make_shared<TimeFrame>(data_times);
1✔
773
        
774
        // Create a separate point data instance for this test
775
        PointData test_point_data;
1✔
776
        test_point_data.addPointsAtTime(TimeFrameIndex(3), points);  // At data timeframe index 3 (time=15)
1✔
777
        
778
        // Query video frame 1 (time=5) which should map to data timeframe index 1 (time=3, closest to 5)
779
        // Since we only have data at index 3, this should return empty
780
        auto result = test_point_data.getPointsAtTime(TimeFrameIndex(1), video_timeframe, data_timeframe);
1✔
781
        
782
        // Should return empty since we don't have data at the converted index
783
        REQUIRE(result.empty());
1✔
784
    }
5✔
785
    
786
    SECTION("Null timeframe handling") {
4✔
787
        std::vector<int> times = {5, 10, 15, 20, 25};
3✔
788
        auto valid_timeframe = std::make_shared<TimeFrame>(times);
1✔
789
        
790
        // This should still work since the function should handle null pointers gracefully
791
        // by falling back to the original behavior
792
        auto result = point_data.getPointsAtTime(TimeFrameIndex(10), nullptr, valid_timeframe);
1✔
793
        
794
        // The behavior when timeframes are null might depend on getTimeIndexForSeries implementation
795
        // For now, let's check that it doesn't crash and returns some result
796
        // The exact behavior would depend on the TimeFrame implementation
797
    }
5✔
798
}
8✔
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