• 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

88.29
/src/DataManager/Points/Point_Data.cpp
1
#include "Point_Data.hpp"
2

3
#include "utils/map_timeseries.hpp"
4
#include "Entity/EntityRegistry.hpp"
5

6
#include <algorithm>
7
#include <iostream>
8
#include <ranges>
9

10
// ========== Constructors ==========
11

12
PointData::PointData(std::map<TimeFrameIndex, Point2D<float>> const & data) {
40✔
13
    for (auto const & [time, point]: data) {
240✔
14
        _data[time].emplace_back(point, 0);
200✔
15
    }
16
}
40✔
17

18
PointData::PointData(std::map<TimeFrameIndex, std::vector<Point2D<float>>> const & data) 
1✔
19
{
20
    for (auto const & [time, points] : data) {
3✔
21
        auto & entries = _data[time];
2✔
22
        entries.reserve(points.size());
2✔
23
        for (auto const & p : points) {
5✔
24
            entries.emplace_back(p, 0);
3✔
25
        }
26
    }
27
}
1✔
28

29
// ========== Setters ==========
30

31
bool PointData::clearAtTime(TimeFrameIndex const time, bool notify) {
17✔
32
    auto it = _data.find(time);
17✔
33
    if (it != _data.end()) {
17✔
34
        _data.erase(it);
16✔
35
        if (notify) {
16✔
36
            notifyObservers();
2✔
37
        }
38
        return true;
16✔
39
    }
40
    return false;
1✔
41
}
42

43
bool PointData::clearAtTime(TimeFrameIndex const time, size_t const index, bool notify) {
×
44
    auto it = _data.find(time);
×
UNCOV
45
    if (it != _data.end()) {
×
46
        if (index >= it->second.size()) {
×
47
            return false;
×
48
        }
UNCOV
49
        it->second.erase(it->second.begin() + static_cast<std::ptrdiff_t>(index));
×
50
        if (it->second.empty()) {
×
51
            _data.erase(it);
×
52
        }
53
        if (notify) {
×
UNCOV
54
            notifyObservers();
×
55
        }
UNCOV
56
        return true;
×
57
    }
UNCOV
58
    return false;
×
59
}
60

61
void PointData::overwritePointAtTime(TimeFrameIndex const time, Point2D<float> const point, bool notify) {
1✔
62
    EntityId entity_id = 0;
1✔
63
    if (_identity_registry) {
1✔
UNCOV
64
        entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::PointEntity, time, 0);
×
65
    }
66
    _data[time].clear();
1✔
67
    _data[time].emplace_back(point, entity_id);
1✔
68
    if (notify) {
1✔
69
        notifyObservers();
1✔
70
    }
71
}
1✔
72

73
void PointData::overwritePointsAtTime(TimeFrameIndex const time, std::vector<Point2D<float>> const & points, bool notify) {
126✔
74
    auto & entries = _data[time];
126✔
75
    entries.clear();
126✔
76
    entries.reserve(points.size());
126✔
77
    for (int i = 0; i < static_cast<int>(points.size()); ++i) {
535✔
78
        EntityId id = 0;
409✔
79
        if (_identity_registry) {
409✔
UNCOV
80
            id = _identity_registry->ensureId(_identity_data_key, EntityKind::PointEntity, time, i);
×
81
        }
82
        entries.emplace_back(points[static_cast<size_t>(i)], id);
409✔
83
    }
84
    if (notify) {
126✔
85
        notifyObservers();
126✔
86
    }
87
}
126✔
88

89
void PointData::overwritePointsAtTimes(
2✔
90
        std::vector<TimeFrameIndex> const & times,
91
        std::vector<std::vector<Point2D<float>>> const & points,
92
        bool notify) {
93
    if (times.size() != points.size()) {
2✔
94
        std::cout << "overwritePointsAtTimes: times and points must be the same size" << std::endl;
1✔
95
        return;
1✔
96
    }
97

98
    for (std::size_t i = 0; i < times.size(); i++) {
3✔
99
        auto const & pts = points[i];
2✔
100
        auto const time = times[i];
2✔
101
        auto & entries = _data[time];
2✔
102
        entries.clear();
2✔
103
        entries.reserve(pts.size());
2✔
104
        for (int j = 0; j < static_cast<int>(pts.size()); ++j) {
5✔
105
            EntityId id = 0;
3✔
106
            if (_identity_registry) {
3✔
UNCOV
107
                id = _identity_registry->ensureId(_identity_data_key, EntityKind::PointEntity, time, j);
×
108
            }
109
            entries.emplace_back(pts[static_cast<size_t>(j)], id);
3✔
110
        }
111
    }
112
    if (notify) {
1✔
113
        notifyObservers();
1✔
114
    }
115
}
116

117
void PointData::addAtTime(TimeFrameIndex const time, Point2D<float> const point, bool notify) {
263✔
118
    int const local_index = static_cast<int>(_data[time].size());
263✔
119
    EntityId entity_id = 0;
263✔
120
    if (_identity_registry) {
263✔
121
        entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::PointEntity, time, local_index);
38✔
122
    }
123
    _data[time].emplace_back(point, entity_id);
263✔
124

125
    if (notify) {
263✔
126
        notifyObservers();
136✔
127
    }
128
}
263✔
129

130
void PointData::addPointsAtTime(TimeFrameIndex const time, std::vector<Point2D<float>> const & points, bool notify) {
148✔
131
    auto & entries = _data[time];
148✔
132
    int const start_index = static_cast<int>(entries.size());
148✔
133
    entries.reserve(entries.size() + points.size());
148✔
134
    for (int i = 0; i < static_cast<int>(points.size()); ++i) {
398✔
135
        EntityId id = 0;
250✔
136
        if (_identity_registry) {
250✔
UNCOV
137
            id = _identity_registry->ensureId(_identity_data_key, EntityKind::PointEntity, time, start_index + i);
×
138
        }
139
        entries.emplace_back(points[static_cast<size_t>(i)], id);
250✔
140
    }
141
    
142
    if (notify) {
148✔
143
        notifyObservers();
146✔
144
    }
145
}
148✔
146

147
void PointData::addPointEntryAtTime(TimeFrameIndex const time,
6✔
148
                                    Point2D<float> const & point,
149
                                    EntityId const entity_id,
150
                                    bool const notify) {
151
    _data[time].emplace_back(point, entity_id);
6✔
152
    if (notify) {
6✔
UNCOV
153
        notifyObservers();
×
154
    }
155
}
6✔
156

157
// ========== Getters ==========
158

159
std::vector<Point2D<float>> const & PointData::getAtTime(TimeFrameIndex const time) const {
179✔
160
    auto it = _data.find(time);
179✔
161
    if (it == _data.end()) {
179✔
162
        return _empty;
31✔
163
    }
164
    _temp_points.clear();
148✔
165
    _temp_points.reserve(it->second.size());
148✔
166
    for (auto const & entry : it->second) {
367✔
167
        _temp_points.push_back(entry.point);
219✔
168
    }
169
    return _temp_points;
148✔
170
}
171

172
std::vector<Point2D<float>> const & PointData::getAtTime(TimeFrameIndex const time, 
17✔
173
                                                        TimeFrame const * source_timeframe,
174
                                                        TimeFrame const * target_timeframe) const {
175
    TimeFrameIndex const converted = convert_time_index(time, source_timeframe, target_timeframe);
17✔
176
    return getAtTime(converted);
34✔
177
}
178

179
std::size_t PointData::getMaxPoints() const {
1✔
180
    std::size_t max_points = 0;
1✔
181
    for (auto const & [time, entries] : _data) {
3✔
182
        (void)time;
183
        max_points = std::max(max_points, entries.size());
2✔
184
    }
185
    return max_points;
1✔
186
}
187

188
// ========== Image Size ==========
189

190
void PointData::changeImageSize(ImageSize const & image_size) {
3✔
191
    if (_image_size.width == -1 || _image_size.height == -1) {
3✔
192
        std::cout << "No size set for current image. "
193
                  << " Please set a valid image size before trying to scale" << std::endl;
1✔
194
        _image_size = image_size; // Set the image size if it wasn't set before
1✔
195
        return;
1✔
196
    }
197

198
    if (_image_size.width == image_size.width && _image_size.height == image_size.height) {
2✔
199
        std::cout << "Image size is the same. No need to scale" << std::endl;
1✔
200
        return;
1✔
201
    }
202

203
    float const scale_x = static_cast<float>(image_size.width) / static_cast<float>(_image_size.width);
1✔
204
    float const scale_y = static_cast<float>(image_size.height) / static_cast<float>(_image_size.height);
1✔
205

206
    for (auto & [time, entries] : _data) {
2✔
207
        (void)time;
208
        for (auto & entry : entries) {
3✔
209
            entry.point.x *= scale_x;
2✔
210
            entry.point.y *= scale_y;
2✔
211
        }
212
    }
213
    _image_size = image_size;
1✔
214
}
215

216
// ========== Copy and Move ==========
217

218
std::size_t PointData::copyTo(PointData& target, TimeFrameInterval const & interval, bool notify) const {
7✔
219
    if (interval.start > interval.end) {
7✔
220
        std::cerr << "PointData::copyTo: interval start (" << interval.start.getValue() 
1✔
221
                  << ") must be <= interval end (" << interval.end.getValue() << ")" << std::endl;
1✔
222
        return 0;
1✔
223
    }
224

225
    std::size_t total_points_copied = 0;
6✔
226

227
    // Iterate through all times in the source data within the interval
228
    for (auto const & [time, entries] : _data) {
28✔
229
        if (time >= interval.start && time <= interval.end && !entries.empty()) {
22✔
230
            for (auto const & entry : entries) {
29✔
231
                target.addAtTime(time, entry.point, false);
19✔
232
                total_points_copied += 1;
19✔
233
            }
234
        }
235
    }
236

237
    // Notify observer only once at the end if requested
238
    if (notify && total_points_copied > 0) {
6✔
239
        target.notifyObservers();
5✔
240
    }
241

242
    return total_points_copied;
6✔
243
}
244

245
std::size_t PointData::copyTo(PointData& target, std::vector<TimeFrameIndex> const& times, bool notify) const {
5✔
246
    std::size_t total_points_copied = 0;
5✔
247

248
    // Copy points for each specified time
249
    for (TimeFrameIndex time : times) {
20✔
250
        auto it = _data.find(time);
15✔
251
        if (it != _data.end() && !it->second.empty()) {
15✔
252
            for (auto const & entry : it->second) {
31✔
253
                target.addAtTime(time, entry.point, false);
21✔
254
                total_points_copied += 1;
21✔
255
            }
256
        }
257
    }
258

259
    // Notify observer only once at the end if requested
260
    if (notify && total_points_copied > 0) {
5✔
261
        target.notifyObservers();
4✔
262
    }
263

264
    return total_points_copied;
5✔
265
}
266

267
std::size_t PointData::moveTo(PointData& target, TimeFrameInterval const & interval, bool notify) {
5✔
268
    if (interval.start > interval.end) {
5✔
UNCOV
269
        std::cerr << "PointData::moveTo: interval start (" << interval.start.getValue() 
×
UNCOV
270
                  << ") must be <= interval end (" << interval.end.getValue() << ")" << std::endl;
×
UNCOV
271
        return 0;
×
272
    }
273

274
    std::size_t total_points_moved = 0;
5✔
275
    std::vector<TimeFrameIndex> times_to_clear;
5✔
276

277
    // First, copy all points in the interval to target
278
    for (auto const & [time, entries] : _data) {
23✔
279
        if (time >= interval.start && time <= interval.end && !entries.empty()) {
18✔
280
            for (auto const & entry : entries) {
27✔
281
                target.addAtTime(time, entry.point, false);
18✔
282
                total_points_moved += 1;
18✔
283
            }
284
            times_to_clear.push_back(time);
9✔
285
        }
286
    }
287

288
    // Then, clear all the times from source
289
    for (TimeFrameIndex time : times_to_clear) {
14✔
290
        (void) clearAtTime(time, false); // Don't notify for each operation
9✔
291
    }
292

293
    // Notify observers only once at the end if requested
294
    if (notify && total_points_moved > 0) {
5✔
295
        target.notifyObservers();
5✔
296
        notifyObservers();
5✔
297
    }
298

299
    return total_points_moved;
5✔
300
}
5✔
301

302
std::size_t PointData::moveTo(PointData& target, std::vector<TimeFrameIndex> const& times, bool notify) {
2✔
303
    std::size_t total_points_moved = 0;
2✔
304
    std::vector<TimeFrameIndex> times_to_clear;
2✔
305

306
    // First, copy points for each specified time to target
307
    for (TimeFrameIndex time : times) {
7✔
308
        auto it = _data.find(time);
5✔
309
        if (it != _data.end() && !it->second.empty()) {
5✔
310
            for (auto const & entry : it->second) {
14✔
311
                target.addAtTime(time, entry.point, false);
9✔
312
                total_points_moved += 1;
9✔
313
            }
314
            times_to_clear.push_back(time);
5✔
315
        }
316
    }
317

318
    // Then, clear all the times from source
319
    for (TimeFrameIndex time : times_to_clear) {
7✔
320
        (void) clearAtTime(time, false); // Don't notify for each operation
5✔
321
    }
322

323
    // Notify observers only once at the end if requested
324
    if (notify && total_points_moved > 0) {
2✔
325
        target.notifyObservers();
2✔
326
        notifyObservers();
2✔
327
    }
328

329
    return total_points_moved;
2✔
330
}
2✔
331

332
void PointData::setIdentityContext(std::string const & data_key, EntityRegistry * registry) {
171✔
333
    _identity_data_key = data_key;
171✔
334
    _identity_registry = registry;
171✔
335
}
171✔
336

337
void PointData::rebuildAllEntityIds() {
171✔
338
    if (!_identity_registry) {
171✔
UNCOV
339
        for (auto & [t, entries] : _data) {
×
340
            (void)t;
UNCOV
341
            for (auto & entry : entries) {
×
UNCOV
342
                entry.entity_id = 0;
×
343
            }
344
        }
UNCOV
345
        return;
×
346
    }
347
    for (auto & [t, entries] : _data) {
521✔
348
        for (int i = 0; i < static_cast<int>(entries.size()); ++i) {
939✔
349
            entries[static_cast<size_t>(i)].entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::PointEntity, t, i);
589✔
350
        }
351
    }
352
}
353

354
std::vector<EntityId> const & PointData::getEntityIdsAtTime(TimeFrameIndex time) const {
77✔
355
    auto it = _data.find(time);
77✔
356
    if (it == _data.end()) {
77✔
UNCOV
357
        return _empty_entity_ids;
×
358
    }
359
    _temp_entity_ids.clear();
77✔
360
    _temp_entity_ids.reserve(it->second.size());
77✔
361
    for (auto const & entry : it->second) {
257✔
362
        _temp_entity_ids.push_back(entry.entity_id);
180✔
363
    }
364
    return _temp_entity_ids;
77✔
365
}
366

367
std::vector<EntityId> PointData::getAllEntityIds() const {
9✔
368
    std::vector<EntityId> out;
9✔
369
    for (auto const & [t, entries] : _data) {
24✔
370
        (void)t;
371
        for (auto const & entry : entries) {
43✔
372
            out.push_back(entry.entity_id);
28✔
373
        }
374
    }
375
    return out;
9✔
UNCOV
376
}
×
377

378
// ========== Entity Lookup Methods ==========
379

380
std::optional<Point2D<float>> PointData::getPointByEntityId(EntityId entity_id) const {
8✔
381
    if (!_identity_registry) {
8✔
UNCOV
382
        return std::nullopt;
×
383
    }
384
    
385
    auto descriptor = _identity_registry->get(entity_id);
8✔
386
    if (!descriptor || descriptor->kind != EntityKind::PointEntity || descriptor->data_key != _identity_data_key) {
8✔
UNCOV
387
        return std::nullopt;
×
388
    }
389
    
390
    TimeFrameIndex time{descriptor->time_value};
8✔
391
    int local_index = descriptor->local_index;
8✔
392
    
393
    auto time_it = _data.find(time);
8✔
394
    if (time_it == _data.end()) {
8✔
UNCOV
395
        return std::nullopt;
×
396
    }
397
    
398
    if (local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
8✔
UNCOV
399
        return std::nullopt;
×
400
    }
401
    
402
    return time_it->second[static_cast<size_t>(local_index)].point;
8✔
403
}
8✔
404

405
std::optional<std::pair<TimeFrameIndex, int>> PointData::getTimeAndIndexByEntityId(EntityId entity_id) const {
5✔
406
    if (!_identity_registry) {
5✔
407
        return std::nullopt;
×
408
    }
409
    
410
    auto descriptor = _identity_registry->get(entity_id);
5✔
411
    if (!descriptor || descriptor->kind != EntityKind::PointEntity || descriptor->data_key != _identity_data_key) {
5✔
UNCOV
412
        return std::nullopt;
×
413
    }
414
    
415
    TimeFrameIndex time{descriptor->time_value};
5✔
416
    int local_index = descriptor->local_index;
5✔
417
    
418
    // Verify the time and index are valid
419
    auto time_it = _data.find(time);
5✔
420
    if (time_it == _data.end() || local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
5✔
421
        return std::nullopt;
×
422
    }
423
    
424
    return std::make_pair(time, local_index);
5✔
425
}
5✔
426

427
std::vector<std::pair<EntityId, Point2D<float>>> PointData::getPointsByEntityIds(std::vector<EntityId> const & entity_ids) const {
1✔
428
    std::vector<std::pair<EntityId, Point2D<float>>> result;
1✔
429
    result.reserve(entity_ids.size());
1✔
430
    
431
    for (EntityId entity_id : entity_ids) {
4✔
432
        auto point = getPointByEntityId(entity_id);
3✔
433
        if (point) {
3✔
434
            result.emplace_back(entity_id, *point);
3✔
435
        }
436
    }
437
    
438
    return result;
1✔
UNCOV
439
}
×
440

441
std::vector<std::tuple<EntityId, TimeFrameIndex, int>> PointData::getTimeInfoByEntityIds(std::vector<EntityId> const & entity_ids) const {
1✔
442
    std::vector<std::tuple<EntityId, TimeFrameIndex, int>> result;
1✔
443
    result.reserve(entity_ids.size());
1✔
444
    
445
    for (EntityId entity_id : entity_ids) {
4✔
446
        auto time_info = getTimeAndIndexByEntityId(entity_id);
3✔
447
        if (time_info) {
3✔
448
            result.emplace_back(entity_id, time_info->first, time_info->second);
3✔
449
        }
450
    }
451
    
452
    return result;
1✔
UNCOV
453
}
×
454

455
// ======== Copy/Move by EntityIds =========
456

457
std::size_t PointData::copyPointsByEntityIds(PointData & target, std::vector<EntityId> const & entity_ids, bool const notify) {
5✔
458
    std::size_t total_points_copied = 0;
5✔
459
    for (auto const & [time, entries] : _data) {
17✔
460
        for (auto const & entry : entries) {
28✔
461
            if (std::ranges::find(entity_ids, entry.entity_id) != entity_ids.end()) {
16✔
462
                target.addAtTime(time, entry.point, false);
6✔
463
                total_points_copied++;
6✔
464
            }
465
        }
466
    }
467
    if (notify && total_points_copied > 0) {
5✔
468
        target.notifyObservers();
3✔
469
    }
470
    return total_points_copied;
5✔
471
}
472

473
std::size_t PointData::movePointsByEntityIds(PointData & target, std::vector<EntityId> const & entity_ids, bool const notify) {
4✔
474
    std::size_t total_points_moved = 0;
4✔
475
    std::vector<std::pair<TimeFrameIndex, size_t>> entries_to_remove;
4✔
476

477
    for (auto const & [time, entries] : _data) {
16✔
478
        for (size_t i = 0; i < entries.size(); ++i) {
28✔
479
            auto const & entry = entries[i];
16✔
480
            if (std::ranges::find(entity_ids, entry.entity_id) != entity_ids.end()) {
16✔
481
                target.addPointEntryAtTime(time, entry.point, entry.entity_id, false);
6✔
482
                entries_to_remove.emplace_back(time, i);
6✔
483
                total_points_moved++;
6✔
484
            }
485
        }
486
    }
487

488
    std::ranges::sort(entries_to_remove,
4✔
489
                      [](auto const & a, auto const & b) {
3✔
490
                          if (a.first != b.first) return a.first > b.first;
3✔
491
                          return a.second > b.second;
2✔
492
                      });
493

494
    for (auto const & [time, index] : entries_to_remove) {
10✔
495
        auto it = _data.find(time);
6✔
496
        if (it != _data.end() && index < it->second.size()) {
6✔
497
            it->second.erase(it->second.begin() + static_cast<long>(index));
6✔
498
            if (it->second.empty()) {
6✔
499
                _data.erase(it);
3✔
500
            }
501
        }
502
    }
503

504
    if (notify && total_points_moved > 0) {
4✔
505
        target.notifyObservers();
3✔
506
        notifyObservers();
3✔
507
    }
508

509
    return total_points_moved;
4✔
510
}
4✔
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