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

paulmthompson / WhiskerToolbox / 18389801194

09 Oct 2025 09:35PM UTC coverage: 71.943% (+0.1%) from 71.826%
18389801194

push

github

paulmthompson
add correlation matrix to filtering interface

207 of 337 new or added lines in 5 files covered. (61.42%)

867 existing lines in 31 files now uncovered.

49964 of 69449 relevant lines covered (71.94%)

1103.53 hits per line

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

98.47
/src/DataManager/Points/Point_Data.test.cpp
1
#include "Points/Point_Data.hpp"
2
#include "TimeFrame/interval_data.hpp"
3
#include "TimeFrame/TimeFrame.hpp"
4
#include "Entity/EntityRegistry.hpp"
5
#include "DataManager.hpp"
6
#include <catch2/catch_test_macros.hpp>
7
#include <catch2/catch_approx.hpp>
8

9
TEST_CASE("DM - PointData - Core functionality", "[points][data][core]") {
17✔
10
    PointData point_data;
17✔
11

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

19
    SECTION("Adding and retrieving points at time") {
17✔
20
        // Add a single point at time 10
21
        point_data.addAtTime(TimeFrameIndex(10), p1);
1✔
22

23
        auto points_at_10 = point_data.getAtTime(TimeFrameIndex(10));
1✔
24
        REQUIRE(points_at_10.size() == 1);
1✔
25
        REQUIRE(points_at_10[0].x == Catch::Approx(1.0f));
1✔
26
        REQUIRE(points_at_10[0].y == Catch::Approx(2.0f));
1✔
27

28
        // Add another point at the same time
29
        point_data.addAtTime(TimeFrameIndex(10), p2);
1✔
30
        points_at_10 = point_data.getAtTime(TimeFrameIndex(10));
1✔
31
        REQUIRE(points_at_10.size() == 2);
1✔
32
        REQUIRE(points_at_10[1].x == Catch::Approx(3.0f));
1✔
33
        REQUIRE(points_at_10[1].y == Catch::Approx(4.0f));
1✔
34

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

43
    SECTION("Overwriting points at time") {
17✔
44
        // Add initial points
45
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
46

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

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

62
    SECTION("Clearing points at time") {
17✔
63
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
64
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
65

66
        static_cast<void>(point_data.clearAtTime(TimeFrameIndex(10)));
1✔
67

68
        auto points_at_10 = point_data.getAtTime(TimeFrameIndex(10));
1✔
69
        auto points_at_20 = point_data.getAtTime(TimeFrameIndex(20));
1✔
70

71
        REQUIRE(points_at_10.empty());
1✔
72
        REQUIRE(points_at_20.size() == 1);
1✔
73
    }
18✔
74

75
    SECTION("GetAllPointsAsRange functionality") {
17✔
76
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
77
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
78

79
        size_t count = 0;
1✔
80
        int64_t first_time = 0;
1✔
81
        int64_t second_time = 0;
1✔
82

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

94
        REQUIRE(count == 2);
1✔
95
        REQUIRE(first_time == 10);
1✔
96
        REQUIRE(second_time == 20);
1✔
97
    }
17✔
98

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

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

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

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

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

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

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

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

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

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

286
    SECTION("Setting and getting image size") {
17✔
287
        ImageSize size{640, 480};
1✔
288
        point_data.setImageSize(size);
1✔
289

290
        auto retrieved_size = point_data.getImageSize();
1✔
291
        REQUIRE(retrieved_size.width == 640);
1✔
292
        REQUIRE(retrieved_size.height == 480);
1✔
293
    }
17✔
294

295
    SECTION("Overwriting points at multiple times") {
17✔
296
        std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20)};
3✔
297
        std::vector<std::vector<Point2D<float>>> points_vec = {points, more_points};
5✔
298

299
        point_data.overwritePointsAtTimes(times, points_vec);
1✔
300

301
        auto points_at_10 = point_data.getAtTime(TimeFrameIndex(10));
1✔
302
        auto points_at_20 = point_data.getAtTime(TimeFrameIndex(20));
1✔
303

304
        REQUIRE(points_at_10.size() == 2);
1✔
305
        REQUIRE(points_at_20.size() == 1);
1✔
306
    }
18✔
307

308
    SECTION("Getting times with points") {
17✔
309
        point_data.addPointsAtTime(TimeFrameIndex(10), points);
1✔
310
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points);
1✔
311

312
        auto times = point_data.getTimesWithData();
1✔
313

314
        REQUIRE(times.size() == 2);
1✔
315
        
316
        // Convert to vector for easier testing or use iterators
317
        auto it = times.begin();
1✔
318
        REQUIRE(*it == TimeFrameIndex(10));
1✔
319
        ++it;
1✔
320
        REQUIRE(*it == TimeFrameIndex(20));
1✔
321
    }
17✔
322

323
    SECTION("Getting max points") {
17✔
324
        point_data.addPointsAtTime(TimeFrameIndex(10), points);      // 2 points
1✔
325
        point_data.addPointsAtTime(TimeFrameIndex(20), more_points); // 1 point
1✔
326

327
        auto max_points = point_data.getMaxPoints();
1✔
328
        REQUIRE(max_points == 2);
1✔
329
    }
17✔
330
}
35✔
331

332
TEST_CASE("PointData - Entity ID handling in copy/move operations", "[points][data][entity][copy][move]") {
3✔
333
    PointData source_data;
3✔
334
    PointData target_data;
3✔
335

336
    // Setup test points
337
    Point2D<float> a{1.0f, 2.0f};
3✔
338
    Point2D<float> b{3.0f, 4.0f};
3✔
339
    Point2D<float> c{5.0f, 6.0f};
3✔
340

341
    // Add test points
342
    source_data.addAtTime(TimeFrameIndex(10), a);
3✔
343
    source_data.addAtTime(TimeFrameIndex(10), b);
3✔
344
    source_data.addAtTime(TimeFrameIndex(20), c);
3✔
345

346
    SECTION("Copy operations create new entity IDs") {
3✔
347
        // Get original entity IDs
348
        auto original_entity_ids = source_data.getAllEntityIds();
1✔
349
        REQUIRE(original_entity_ids.size() == 3);
1✔
350

351
        // Copy data
352
        TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(20)};
1✔
353
        source_data.copyTo(target_data, interval);
1✔
354

355
        // Get new entity IDs from target
356
        auto target_entity_ids = target_data.getAllEntityIds();
1✔
357
        REQUIRE(target_entity_ids.size() == 3);
1✔
358

359
        // Verify source entity IDs are unchanged
360
        auto source_entity_ids_after = source_data.getAllEntityIds();
1✔
361
        REQUIRE(source_entity_ids_after == original_entity_ids);
1✔
362
    }
4✔
363

364
    SECTION("Move operations create new entity IDs in target") {
3✔
365
        // Get original entity IDs
366
        auto original_entity_ids = source_data.getAllEntityIds();
1✔
367
        REQUIRE(original_entity_ids.size() == 3);
1✔
368

369
        // Move data
370
        TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(10)};
1✔
371
        source_data.moveTo(target_data, interval);
1✔
372

373
        // Get new entity IDs from target
374
        auto target_entity_ids = target_data.getAllEntityIds();
1✔
375
        REQUIRE(target_entity_ids.size() == 2); // 2 points at time 10
1✔
376

377
        // Verify source still has remaining data with original entity IDs
378
        auto remaining_entity_ids = source_data.getAllEntityIds();
1✔
379
        REQUIRE(remaining_entity_ids.size() == 1); // 1 point at time 20
1✔
380
    }
4✔
381

382
    SECTION("Entity ID consistency within time frames") {
3✔
383
        PointData pd;
1✔
384
        pd.addAtTime(TimeFrameIndex(10), a);
1✔
385
        pd.addAtTime(TimeFrameIndex(10), b);
1✔
386
        pd.addAtTime(TimeFrameIndex(30), a);
1✔
387
        pd.addAtTime(TimeFrameIndex(30), b);
1✔
388

389
        auto entity_ids_10 = pd.getEntityIdsAtTime(TimeFrameIndex(10));
1✔
390
        auto entity_ids_30 = pd.getEntityIdsAtTime(TimeFrameIndex(30));
1✔
391

392
        REQUIRE(entity_ids_10.size() == 2);
1✔
393
        REQUIRE(entity_ids_30.size() == 2);
1✔
394
    }
4✔
395
}
6✔
396

397
TEST_CASE("PointData - Copy and Move by EntityID", "[points][data][entity][copy][move][by_id]") {
9✔
398
    // Setup test points
399
    Point2D<float> p1{1.0f, 2.0f};
9✔
400
    Point2D<float> p2{3.0f, 4.0f};
9✔
401
    Point2D<float> p3{5.0f, 6.0f};
9✔
402
    Point2D<float> p4{7.0f, 8.0f};
9✔
403

404
    auto data_manager = std::make_unique<DataManager>();
9✔
405
    auto time_frame = std::make_shared<TimeFrame>(std::vector<int>{0, 10, 20, 30});
27✔
406
    data_manager->setTime(TimeKey("test_time"), time_frame);
9✔
407

408
    SECTION("Copy points by EntityID - basic functionality") {
9✔
409
        // Setup fresh source and target data
410
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
411
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
412

413
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
414
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
415

416
        // Add test points
417
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
418
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
419
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
420
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
421

422
        // Get entity IDs for testing
423
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
424
        REQUIRE(entity_ids_10.size() == 2);
1✔
425

426
        // Copy points from time 10 (2 points)
427
        std::size_t points_copied = source_data->copyPointsByEntityIds(*target_data, entity_ids_10);
1✔
428

429
        REQUIRE(points_copied == 2);
1✔
430

431
        // Verify source data is unchanged
432
        REQUIRE(source_data->getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
433
        REQUIRE(source_data->getAtTime(TimeFrameIndex(20)).size() == 1);
1✔
434
        REQUIRE(source_data->getAtTime(TimeFrameIndex(30)).size() == 1);
1✔
435

436
        // Verify target has copied data
437
        REQUIRE(target_data->getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
438
        REQUIRE(target_data->getAtTime(TimeFrameIndex(20)).size() == 0);
1✔
439
        REQUIRE(target_data->getAtTime(TimeFrameIndex(30)).size() == 0);
1✔
440

441
        // Verify target has new entity IDs
442
        auto target_entity_ids = target_data->getAllEntityIds();
1✔
443
        REQUIRE(target_entity_ids.size() == 2);
1✔
444
        REQUIRE(target_entity_ids != entity_ids_10);
1✔
445
    }
10✔
446

447
    SECTION("Copy points by EntityID - mixed times") {
9✔
448
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
449
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
450

451
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
452
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
453

454
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
455
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
456
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
457
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
458

459
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
460
        auto entity_ids_20 = source_data->getEntityIdsAtTime(TimeFrameIndex(20));
1✔
461
        REQUIRE(entity_ids_10.size() == 2);
1✔
462
        REQUIRE(entity_ids_20.size() == 1);
1✔
463

464
        std::vector<EntityId> mixed_entity_ids = {entity_ids_10[0], entity_ids_20[0]};
3✔
465
        std::size_t points_copied = source_data->copyPointsByEntityIds(*target_data, mixed_entity_ids);
1✔
466

467
        REQUIRE(points_copied == 2);
1✔
468
        REQUIRE(target_data->getAtTime(TimeFrameIndex(10)).size() == 1);
1✔
469
        REQUIRE(target_data->getAtTime(TimeFrameIndex(20)).size() == 1);
1✔
470
        REQUIRE(target_data->getAtTime(TimeFrameIndex(30)).size() == 0);
1✔
471
    }
10✔
472

473
    SECTION("Copy points by EntityID - non-existent EntityIDs") {
9✔
474
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
475
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
476

477
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
478
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
479

480
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
481
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
482
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
483
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
484

485
        std::vector<EntityId> fake_entity_ids = {99999, 88888};
3✔
486
        std::size_t points_copied = source_data->copyPointsByEntityIds(*target_data, fake_entity_ids);
1✔
487

488
        REQUIRE(points_copied == 0);
1✔
489
        REQUIRE(target_data->getTimesWithData().empty());
1✔
490
    }
10✔
491

492
    SECTION("Copy points by EntityID - empty EntityID list") {
9✔
493
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
494
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
495

496
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
497
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
498

499
        std::vector<EntityId> empty_entity_ids;
1✔
500
        std::size_t points_copied = source_data->copyPointsByEntityIds(*target_data, empty_entity_ids);
1✔
501

502
        REQUIRE(points_copied == 0);
1✔
503
        REQUIRE(target_data->getTimesWithData().empty());
1✔
504
    }
10✔
505

506
    SECTION("Move points by EntityID - basic functionality") {
9✔
507
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
508
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
509

510
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
511
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
512

513
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
514
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
515
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
516
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
517

518
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
519
        REQUIRE(entity_ids_10.size() == 2);
1✔
520

521
        std::size_t points_moved = source_data->movePointsByEntityIds(*target_data, entity_ids_10);
1✔
522

523
        REQUIRE(points_moved == 2);
1✔
524
        REQUIRE(source_data->getAtTime(TimeFrameIndex(10)).size() == 0);
1✔
525
        REQUIRE(source_data->getAtTime(TimeFrameIndex(20)).size() == 1);
1✔
526
        REQUIRE(source_data->getAtTime(TimeFrameIndex(30)).size() == 1);
1✔
527

528
        REQUIRE(target_data->getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
529
        REQUIRE(target_data->getAtTime(TimeFrameIndex(20)).size() == 0);
1✔
530
        REQUIRE(target_data->getAtTime(TimeFrameIndex(30)).size() == 0);
1✔
531

532
        auto target_entity_ids = target_data->getAllEntityIds();
1✔
533
        REQUIRE(target_entity_ids.size() == 2);
1✔
534
        REQUIRE(target_entity_ids == entity_ids_10);
1✔
535
    }
10✔
536

537
    SECTION("Move points by EntityID - mixed times") {
9✔
538
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
539
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
540

541
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
542
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
543

544
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
545
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
546
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
547
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
548

549
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
550
        auto entity_ids_20 = source_data->getEntityIdsAtTime(TimeFrameIndex(20));
1✔
551

552
        std::vector<EntityId> mixed_entity_ids = {entity_ids_10[0], entity_ids_20[0]};
3✔
553
        std::size_t points_moved = source_data->movePointsByEntityIds(*target_data, mixed_entity_ids);
1✔
554

555
        REQUIRE(points_moved == 2);
1✔
556
        REQUIRE(source_data->getAtTime(TimeFrameIndex(10)).size() == 1);
1✔
557
        REQUIRE(source_data->getAtTime(TimeFrameIndex(20)).size() == 0);
1✔
558
        REQUIRE(source_data->getAtTime(TimeFrameIndex(30)).size() == 1);
1✔
559

560
        REQUIRE(target_data->getAtTime(TimeFrameIndex(10)).size() == 1);
1✔
561
        REQUIRE(target_data->getAtTime(TimeFrameIndex(20)).size() == 1);
1✔
562
        REQUIRE(target_data->getAtTime(TimeFrameIndex(30)).size() == 0);
1✔
563
    }
10✔
564

565
    SECTION("Move points by EntityID - non-existent EntityIDs") {
9✔
566
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
567
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
568

569
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
570
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
571

572
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
573
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
574
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
575
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
576

577
        std::vector<EntityId> fake_entity_ids = {99999, 88888};
3✔
578
        std::size_t points_moved = source_data->movePointsByEntityIds(*target_data, fake_entity_ids);
1✔
579

580
        REQUIRE(points_moved == 0);
1✔
581
        REQUIRE(target_data->getTimesWithData().empty());
1✔
582

583
        REQUIRE(source_data->getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
584
        REQUIRE(source_data->getAtTime(TimeFrameIndex(20)).size() == 1);
1✔
585
        REQUIRE(source_data->getAtTime(TimeFrameIndex(30)).size() == 1);
1✔
586
    }
10✔
587

588
    SECTION("Copy preserves point data integrity") {
9✔
589
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
590
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
591

592
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
593
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
594

595
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
596
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
597
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
598
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
599

600
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
601
        source_data->copyPointsByEntityIds(*target_data, entity_ids_10);
1✔
602

603
        auto source_points = source_data->getAtTime(TimeFrameIndex(10));
1✔
604
        auto target_points = target_data->getAtTime(TimeFrameIndex(10));
1✔
605

606
        REQUIRE(source_points.size() == target_points.size());
1✔
607
        for (size_t i = 0; i < source_points.size(); ++i) {
3✔
608
            REQUIRE(source_points[i].x == Catch::Approx(target_points[i].x));
2✔
609
            REQUIRE(source_points[i].y == Catch::Approx(target_points[i].y));
2✔
610
        }
611
    }
10✔
612

613
    SECTION("Move preserves point data integrity") {
9✔
614
        data_manager->setData<PointData>("target_data", TimeKey("test_time"));
3✔
615
        data_manager->setData<PointData>("source_data", TimeKey("test_time"));
3✔
616

617
        auto source_data = data_manager->getData<PointData>("source_data");
3✔
618
        auto target_data = data_manager->getData<PointData>("target_data");
3✔
619

620
        source_data->addAtTime(TimeFrameIndex(10), p1);
1✔
621
        source_data->addAtTime(TimeFrameIndex(10), p2);
1✔
622
        source_data->addAtTime(TimeFrameIndex(20), p3);
1✔
623
        source_data->addAtTime(TimeFrameIndex(30), p4);
1✔
624

625
        auto entity_ids_10 = source_data->getEntityIdsAtTime(TimeFrameIndex(10));
1✔
626
        auto original_points = source_data->getAtTime(TimeFrameIndex(10));
1✔
627
        REQUIRE(original_points.size() == 2);
1✔
628

629
        source_data->movePointsByEntityIds(*target_data, entity_ids_10);
1✔
630

631
        auto target_points = target_data->getAtTime(TimeFrameIndex(10));
1✔
632
        REQUIRE(target_points.size() == 2);
1✔
633

634
        // Verify each original point is present in target (order may differ)
635
        for (auto const & sp : original_points) {
3✔
636
            bool found = false;
2✔
637
            for (auto const & tp : target_points) {
3✔
638
                if (sp.x == tp.x && sp.y == tp.y) { found = true; break; }
3✔
639
            }
640
            REQUIRE(found);
2✔
641
        }
642
    }
10✔
643
}
18✔
644

645
TEST_CASE("DM - PointData - Edge cases and error handling", "[points][data][error]") {
7✔
646
    PointData point_data;
7✔
647

648
    SECTION("Getting points at non-existent time") {
7✔
649
        auto points = point_data.getAtTime(TimeFrameIndex(999));
1✔
650
        REQUIRE(points.empty());
1✔
651
    }
8✔
652

653
    SECTION("Clearing points at non-existent time") {
7✔
654
        static_cast<void>(point_data.clearAtTime(TimeFrameIndex(42)));
1✔
655
        auto points = point_data.getAtTime(TimeFrameIndex(42));
1✔
656
        REQUIRE(points.empty());
1✔
657

658
        // Verify the time was NOT created
659
        bool found = false;
1✔
660
        for (const auto& pair : point_data.GetAllPointsAsRange()) {
1✔
UNCOV
661
            if (pair.time.getValue() == 42) {
×
UNCOV
662
                found = true;
×
UNCOV
663
                break;
×
664
            }
UNCOV
665
        }
×
666
        REQUIRE(found == false);
1✔
667
    }
8✔
668

669
    SECTION("Empty range with no data") {
7✔
670
        auto range = point_data.GetAllPointsAsRange();
1✔
671
        size_t count = 0;
1✔
672

673
        for (auto const& pair : range) {
1✔
UNCOV
674
            count++;
×
UNCOV
675
        }
×
676

677
        REQUIRE(count == 0);
1✔
678
    }
7✔
679

680
    SECTION("Adding empty points vector") {
7✔
681
        std::vector<Point2D<float>> empty_points;
1✔
682
        point_data.addPointsAtTime(TimeFrameIndex(10), empty_points);
1✔
683

684
        auto points = point_data.getAtTime(TimeFrameIndex(10));
1✔
685
        REQUIRE(points.empty());
1✔
686

687
        // Verify the time was created
688
        bool found = false;
1✔
689
        for (const auto& pair : point_data.GetAllPointsAsRange()) {
1✔
690
            if (pair.time.getValue() == 10) {
1✔
691
                found = true;
1✔
692
                break;
1✔
693
            }
694
        }
1✔
695
        REQUIRE(found);
1✔
696
    }
8✔
697

698
    SECTION("Overwriting points at times with mismatched vectors") {
7✔
699
        std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20), TimeFrameIndex(30)};
3✔
700
        std::vector<std::vector<Point2D<float>>> points = {
1✔
701
                {{1.0f, 2.0f}},
702
                {{3.0f, 4.0f}}
703
        }; // Only 2 elements
6✔
704

705
        // This should not modify the data
706
        point_data.overwritePointsAtTimes(times, points);
1✔
707

708
        auto range = point_data.GetAllPointsAsRange();
1✔
709
        size_t count = 0;
1✔
710
        for (auto const& pair : range) {
1✔
UNCOV
711
            count++;
×
UNCOV
712
        }
×
713

714
        // No points should be added since we had a size mismatch
715
        REQUIRE(count == 0);
1✔
716
    }
8✔
717

718
    SECTION("Multiple operations sequence") {
7✔
719
        Point2D<float> p1{1.0f, 2.0f};
1✔
720

721
        // Add, clear, add again to test internal state consistency
722
        point_data.addAtTime(TimeFrameIndex(5), p1);
1✔
723
        static_cast<void>(point_data.clearAtTime(TimeFrameIndex(5)));
1✔
724
        point_data.addAtTime(TimeFrameIndex(5), p1);
1✔
725

726
        auto points = point_data.getAtTime(TimeFrameIndex(5));
1✔
727
        REQUIRE(points.size() == 1);
1✔
728
        REQUIRE(points[0].x == Catch::Approx(1.0f));
1✔
729
    }
8✔
730

731
    SECTION("Construction from map") {
7✔
732
        std::map<TimeFrameIndex, std::vector<Point2D<float>>> map_data = {
1✔
733
                {TimeFrameIndex(10), {{1.0f, 2.0f}, {3.0f, 4.0f}}},
2✔
734
                {TimeFrameIndex(20), {{5.0f, 6.0f}}}
2✔
735
        };
7✔
736

737
        PointData point_data_from_map(map_data);
1✔
738

739
        auto points_at_10 = point_data_from_map.getAtTime(TimeFrameIndex(10));
1✔
740
        auto points_at_20 = point_data_from_map.getAtTime(TimeFrameIndex(20));
1✔
741

742
        REQUIRE(points_at_10.size() == 2);
1✔
743
        REQUIRE(points_at_20.size() == 1);
1✔
744
        REQUIRE(points_at_10[0].x == Catch::Approx(1.0f));
1✔
745
        REQUIRE(points_at_20[0].x == Catch::Approx(5.0f));
1✔
746
    }
8✔
747
}
25✔
748

749
TEST_CASE("DM - PointData - Copy and Move operations", "[points][data][copy][move]") {
17✔
750
    PointData source_data;
17✔
751
    PointData target_data;
17✔
752

753
    // Setup test data
754
    std::vector<Point2D<float>> points_10 = {{1.0f, 2.0f}, {3.0f, 4.0f}};
51✔
755
    std::vector<Point2D<float>> points_15 = {{5.0f, 6.0f}};
51✔
756
    std::vector<Point2D<float>> points_20 = {{7.0f, 8.0f}, {9.0f, 10.0f}, {11.0f, 12.0f}};
51✔
757
    std::vector<Point2D<float>> points_25 = {{13.0f, 14.0f}};
51✔
758

759
    source_data.addPointsAtTime(TimeFrameIndex(10), points_10);
17✔
760
    source_data.addPointsAtTime(TimeFrameIndex(15), points_15);
17✔
761
    source_data.addPointsAtTime(TimeFrameIndex(20), points_20);
17✔
762
    source_data.addPointsAtTime(TimeFrameIndex(25), points_25);
17✔
763

764
    SECTION("copyTo - time range operations") {
17✔
765
        SECTION("Copy entire range") {
5✔
766
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(25)};
1✔
767
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
768
            
769
            REQUIRE(copied == 7); // 2 + 1 + 3 + 1 = 7 points total
1✔
770
            
771
            // Verify all points were copied
772
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
773
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
774
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
775
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
776
            
777
            // Verify source data is unchanged
778
            REQUIRE(source_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
779
            REQUIRE(source_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
780
        }
5✔
781

782
        SECTION("Copy partial range") {
5✔
783
            TimeFrameInterval interval{TimeFrameIndex(15), TimeFrameIndex(20)};
1✔
784
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
785
            
786
            REQUIRE(copied == 4); // 1 + 3 = 4 points
1✔
787
            
788
            // Verify only points in range were copied
789
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).empty());
1✔
790
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
791
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
792
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).empty());
1✔
793
        }
5✔
794

795
        SECTION("Copy single time point") {
5✔
796
            TimeFrameInterval interval{TimeFrameIndex(20), TimeFrameIndex(20)};
1✔
797
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
798
            
799
            REQUIRE(copied == 3);
1✔
800
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
801
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).empty());
1✔
802
        }
5✔
803

804
        SECTION("Copy non-existent range") {
5✔
805
            TimeFrameInterval interval{TimeFrameIndex(100), TimeFrameIndex(200)};
1✔
806
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
807
            
808
            REQUIRE(copied == 0);
1✔
809
            REQUIRE(target_data.getTimesWithData().empty());
1✔
810
        }
5✔
811

812
        SECTION("Copy with invalid range (start > end)") {
5✔
813
            TimeFrameInterval interval{TimeFrameIndex(25), TimeFrameIndex(10)};
1✔
814
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
815
            
816
            REQUIRE(copied == 0);
1✔
817
            REQUIRE(target_data.getTimesWithData().empty());
1✔
818
        }
5✔
819
    }
17✔
820

821
    SECTION("copyTo - specific times vector operations") {
17✔
822
        SECTION("Copy multiple specific times") {
5✔
823
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20)};
3✔
824
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
825
            
826
            REQUIRE(copied == 5); // 2 + 3 = 5 points
1✔
827
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
828
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).empty());
1✔
829
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
830
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).empty());
1✔
831
        }
6✔
832

833
        SECTION("Copy times in non-sequential order") {
5✔
834
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(25), TimeFrameIndex(10), TimeFrameIndex(15)}; // Non-sequential
3✔
835
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
836
            
837
            REQUIRE(copied == 4); // 1 + 2 + 1 = 4 points
1✔
838
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
839
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
840
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
841
        }
6✔
842

843
        SECTION("Copy with duplicate times in vector") {
5✔
844
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(10), TimeFrameIndex(20)}; // Duplicate time
3✔
845
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
846
            
847
            // Should copy 10 twice and 20 once
848
            REQUIRE(copied == 7); // 2 + 2 + 3 = 7 points (10 copied twice)
1✔
849
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 4); // 2 + 2 = 4 (added twice)
1✔
850
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
851
        }
6✔
852

853
        SECTION("Copy non-existent times") {
5✔
854
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(100), TimeFrameIndex(200), TimeFrameIndex(300)};
3✔
855
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
856
            
857
            REQUIRE(copied == 0);
1✔
858
            REQUIRE(target_data.getTimesWithData().empty());
1✔
859
        }
6✔
860

861
        SECTION("Copy mixed existent and non-existent times") {
5✔
862
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(100), TimeFrameIndex(20), TimeFrameIndex(200)};
3✔
863
            std::size_t copied = source_data.copyTo(target_data, times);
1✔
864
            
865
            REQUIRE(copied == 5); // Only times 10 and 20 exist
1✔
866
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
867
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
868
        }
6✔
869
    }
17✔
870

871
    SECTION("moveTo - time range operations") {
17✔
872
        SECTION("Move entire range") {
3✔
873
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(25)};
1✔
874
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
875
            
876
            REQUIRE(moved == 7); // 2 + 1 + 3 + 1 = 7 points total
1✔
877
            
878
            // Verify all points were moved to target
879
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
880
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
881
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
882
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
883
            
884
            // Verify source data is now empty
885
            REQUIRE(source_data.getTimesWithData().empty());
1✔
886
        }
3✔
887

888
        SECTION("Move partial range") {
3✔
889
            TimeFrameInterval interval{TimeFrameIndex(15), TimeFrameIndex(20)};
1✔
890
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
891
            
892
            REQUIRE(moved == 4); // 1 + 3 = 4 points
1✔
893
            
894
            // Verify only points in range were moved
895
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
896
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
897
            
898
            // Verify source still has data outside the range
899
            REQUIRE(source_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
900
            REQUIRE(source_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
901
            REQUIRE(source_data.getAtTime(TimeFrameIndex(15)).empty());
1✔
902
            REQUIRE(source_data.getAtTime(TimeFrameIndex(20)).empty());
1✔
903
        }
3✔
904

905
        SECTION("Move single time point") {
3✔
906
            TimeFrameInterval interval{TimeFrameIndex(20), TimeFrameIndex(20)};
1✔
907
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
908
            
909
            REQUIRE(moved == 3);
1✔
910
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
911
            
912
            // Verify only time 20 was removed from source
913
            REQUIRE(source_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
914
            REQUIRE(source_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
915
            REQUIRE(source_data.getAtTime(TimeFrameIndex(20)).empty());
1✔
916
            REQUIRE(source_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
917
        }
3✔
918
    }
17✔
919

920
    SECTION("moveTo - specific times vector operations") {
17✔
921
        SECTION("Move multiple specific times") {
2✔
922
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(10), TimeFrameIndex(20)};
3✔
923
            std::size_t moved = source_data.moveTo(target_data, times);
1✔
924
            
925
            REQUIRE(moved == 5); // 2 + 3 = 5 points
1✔
926
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
927
            REQUIRE(target_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
928
            
929
            // Verify moved times are cleared from source
930
            REQUIRE(source_data.getAtTime(TimeFrameIndex(10)).empty());
1✔
931
            REQUIRE(source_data.getAtTime(TimeFrameIndex(20)).empty());
1✔
932
            // But other times remain
933
            REQUIRE(source_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
934
            REQUIRE(source_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
935
        }
3✔
936

937
        SECTION("Move times in non-sequential order") {
2✔
938
            std::vector<TimeFrameIndex> times = {TimeFrameIndex(25), TimeFrameIndex(10), TimeFrameIndex(15)}; // Non-sequential
3✔
939
            std::size_t moved = source_data.moveTo(target_data, times);
1✔
940
            
941
            REQUIRE(moved == 4); // 1 + 2 + 1 = 4 points
1✔
942
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 2);
1✔
943
            REQUIRE(target_data.getAtTime(TimeFrameIndex(15)).size() == 1);
1✔
944
            REQUIRE(target_data.getAtTime(TimeFrameIndex(25)).size() == 1);
1✔
945
            
946
            // Only time 20 should remain in source
947
            REQUIRE(source_data.getTimesWithData().size() == 1);
1✔
948
            REQUIRE(source_data.getAtTime(TimeFrameIndex(20)).size() == 3);
1✔
949
        }
3✔
950
    }
17✔
951

952
    SECTION("Copy/Move to target with existing data") {
17✔
953
        // Add some existing data to target
954
        std::vector<Point2D<float>> existing_points = {{100.0f, 200.0f}};
6✔
955
        target_data.addPointsAtTime(TimeFrameIndex(10), existing_points);
2✔
956

957
        SECTION("Copy to existing time adds points") {
2✔
958
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(10)};
1✔
959
            std::size_t copied = source_data.copyTo(target_data, interval);
1✔
960
            
961
            REQUIRE(copied == 2);
1✔
962
            // Should have existing point plus copied points
963
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 3); // 1 existing + 2 copied
1✔
964
        }
2✔
965

966
        SECTION("Move to existing time adds points") {
2✔
967
            TimeFrameInterval interval{TimeFrameIndex(10), TimeFrameIndex(10)};
1✔
968
            std::size_t moved = source_data.moveTo(target_data, interval);
1✔
969
            
970
            REQUIRE(moved == 2);
1✔
971
            // Should have existing point plus moved points
972
            REQUIRE(target_data.getAtTime(TimeFrameIndex(10)).size() == 3); // 1 existing + 2 moved
1✔
973
            
974
            // Source should no longer have points at time 10
975
            REQUIRE(source_data.getAtTime(TimeFrameIndex(10)).empty());
1✔
976
        }
2✔
977
    }
19✔
978
}
34✔
979

980
TEST_CASE("DM - PointData - Image scaling", "[points][data][scaling]") {
3✔
981
    PointData point_data;
3✔
982
    
983
    // Setup test data with known coordinates
984
    std::vector<Point2D<float>> points = {{100.0f, 200.0f}, {300.0f, 400.0f}};
9✔
985
    point_data.addPointsAtTime(TimeFrameIndex(10), points);
3✔
986
    
987
    SECTION("Scaling from known size") {
3✔
988
        // Set initial image size
989
        ImageSize initial_size{640, 480};
1✔
990
        point_data.setImageSize(initial_size);
1✔
991
        
992
        // Scale to larger size
993
        ImageSize new_size{1280, 960};
1✔
994
        point_data.changeImageSize(new_size);
1✔
995
        
996
        auto scaled_points = point_data.getAtTime(TimeFrameIndex(10));
1✔
997
        
998
        // Points should be scaled by factor of 2
999
        REQUIRE(scaled_points[0].x == Catch::Approx(200.0f));
1✔
1000
        REQUIRE(scaled_points[0].y == Catch::Approx(400.0f));
1✔
1001
        REQUIRE(scaled_points[1].x == Catch::Approx(600.0f));
1✔
1002
        REQUIRE(scaled_points[1].y == Catch::Approx(800.0f));
1✔
1003
        
1004
        // Image size should be updated
1005
        auto current_size = point_data.getImageSize();
1✔
1006
        REQUIRE(current_size.width == 1280);
1✔
1007
        REQUIRE(current_size.height == 960);
1✔
1008
    }
4✔
1009
    
1010
    SECTION("Scaling with no initial size set") {
3✔
1011
        // Try to scale without setting initial size
1012
        ImageSize new_size{1280, 960};
1✔
1013
        point_data.changeImageSize(new_size);
1✔
1014
        
1015
        // Points should remain unchanged since no initial size was set
1016
        auto unchanged_points = point_data.getAtTime(TimeFrameIndex(10));
1✔
1017
        REQUIRE(unchanged_points[0].x == Catch::Approx(100.0f));
1✔
1018
        REQUIRE(unchanged_points[0].y == Catch::Approx(200.0f));
1✔
1019
        
1020
        // But image size should now be set
1021
        auto current_size = point_data.getImageSize();
1✔
1022
        REQUIRE(current_size.width == 1280);
1✔
1023
        REQUIRE(current_size.height == 960);
1✔
1024
    }
4✔
1025
    
1026
    SECTION("Scaling to same size does nothing") {
3✔
1027
        ImageSize size{640, 480};
1✔
1028
        point_data.setImageSize(size);
1✔
1029
        
1030
        // Scale to same size
1031
        point_data.changeImageSize(size);
1✔
1032
        
1033
        // Points should remain unchanged
1034
        auto unchanged_points = point_data.getAtTime(TimeFrameIndex(10));
1✔
1035
        REQUIRE(unchanged_points[0].x == Catch::Approx(100.0f));
1✔
1036
        REQUIRE(unchanged_points[0].y == Catch::Approx(200.0f));
1✔
1037
    }
4✔
1038
}
6✔
1039

1040
TEST_CASE("PointData - Timeframe conversion", "[points][data][timeframe]") {
4✔
1041
    PointData point_data;
4✔
1042
    
1043
    // Setup test data
1044
    std::vector<Point2D<float>> points = {{100.0f, 200.0f}, {300.0f, 400.0f}};
12✔
1045
    point_data.addPointsAtTime(TimeFrameIndex(10), points);
4✔
1046
    point_data.addPointsAtTime(TimeFrameIndex(20), points);
4✔
1047
    
1048
    SECTION("Same timeframe returns original data") {
4✔
1049
        // Create a single timeframe
1050
        std::vector<int> times = {5, 10, 15, 20, 25};
3✔
1051
        auto timeframe = std::make_shared<TimeFrame>(times);
1✔
1052
        
1053
        // Query with same source and target timeframe
1054
        auto result = point_data.getAtTime(TimeFrameIndex(10), timeframe.get(), timeframe.get());
1✔
1055
        
1056
        REQUIRE(result.size() == 2);
1✔
1057
        REQUIRE(result[0].x == Catch::Approx(100.0f));
1✔
1058
        REQUIRE(result[0].y == Catch::Approx(200.0f));
1✔
1059
    }
5✔
1060
    
1061
    SECTION("Different timeframes with conversion") {
4✔
1062
        // Create source timeframe (e.g., video frames)
1063
        std::vector<int> video_times = {0, 10, 20, 30, 40};  // Video at 1 Hz
3✔
1064
        auto video_timeframe = std::make_shared<TimeFrame>(video_times);
1✔
1065
        
1066
        // Create target timeframe (e.g., data sampling at higher rate)
1067
        std::vector<int> data_times = {0, 5, 10, 15, 20, 25, 30, 35, 40}; // Data at 2 Hz
3✔
1068
        auto data_timeframe = std::make_shared<TimeFrame>(data_times);
1✔
1069
        
1070
        // Clear existing data and add data at the correct indices for the target timeframe
1071
        PointData test_point_data;
1✔
1072
        
1073
        // Video frame 1 (time=10) should map to data_timeframe index 2 (time=10)
1074
        // Video frame 2 (time=20) should map to data_timeframe index 4 (time=20)
1075
        test_point_data.addPointsAtTime(TimeFrameIndex(2), points);  // At data timeframe index 2 (time=10)
1✔
1076
        test_point_data.addPointsAtTime(TimeFrameIndex(4), points);  // At data timeframe index 4 (time=20)
1✔
1077
        
1078
        // Query video frame 1 (time=10) which should map to data index 2 (time=10)
1079
        auto result = test_point_data.getAtTime(TimeFrameIndex(1), 
1✔
1080
                                                video_timeframe.get(), 
1✔
1081
                                                data_timeframe.get());
2✔
1082
        
1083
        REQUIRE(result.size() == 2);
1✔
1084
        REQUIRE(result[0].x == Catch::Approx(100.0f));
1✔
1085
        REQUIRE(result[0].y == Catch::Approx(200.0f));
1✔
1086
    }
5✔
1087
    
1088
    SECTION("Timeframe conversion with no matching data") {
4✔
1089
        // Create timeframes where conversion maps to non-existent data
1090
        std::vector<int> video_times = {0, 5, 10};
3✔
1091
        auto video_timeframe = std::make_shared<TimeFrame>(video_times);
1✔
1092
        
1093
        std::vector<int> data_times = {0, 3, 7, 15, 25};
3✔
1094
        auto data_timeframe = std::make_shared<TimeFrame>(data_times);
1✔
1095
        
1096
        // Create a separate point data instance for this test
1097
        PointData test_point_data;
1✔
1098
        test_point_data.addPointsAtTime(TimeFrameIndex(3), points);  // At data timeframe index 3 (time=15)
1✔
1099
        
1100
        // Query video frame 1 (time=5) which should map to data timeframe index 1 (time=3, closest to 5)
1101
        // Since we only have data at index 3, this should return empty
1102
        auto result = test_point_data.getAtTime(TimeFrameIndex(1), video_timeframe.get(), data_timeframe.get());
1✔
1103
        
1104
        // Should return empty since we don't have data at the converted index
1105
        REQUIRE(result.empty());
1✔
1106
    }
5✔
1107
    
1108
    SECTION("Null timeframe handling") {
4✔
1109
        std::vector<int> times = {5, 10, 15, 20, 25};
3✔
1110
        auto valid_timeframe = std::make_shared<TimeFrame>(times);
1✔
1111
        
1112
        // This should still work since the function should handle null pointers gracefully
1113
        // by falling back to the original behavior
1114
        auto result = point_data.getAtTime(TimeFrameIndex(10), nullptr, valid_timeframe.get());
1✔
1115
        
1116
        // The behavior when timeframes are null might depend on getTimeIndexForSeries implementation
1117
        // For now, let's check that it doesn't crash and returns some result
1118
        // The exact behavior would depend on the TimeFrame implementation
1119
    }
5✔
1120
}
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