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

paulmthompson / WhiskerToolbox / 18685379784

21 Oct 2025 01:25PM UTC coverage: 72.522% (+0.1%) from 72.391%
18685379784

push

github

paulmthompson
fix failing tests

18 of 40 new or added lines in 1 file covered. (45.0%)

1765 existing lines in 32 files now uncovered.

53998 of 74457 relevant lines covered (72.52%)

46177.73 hits per line

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

97.56
/src/DataManager/Points/Point_Data.hpp
1
#ifndef POINT_DATA_HPP
2
#define POINT_DATA_HPP
3

4
#include "CoreGeometry/ImageSize.hpp"
5
#include "CoreGeometry/points.hpp"
6
#include "Observer/Observer_Data.hpp"
7
#include "TimeFrame/TimeFrame.hpp"
8
#include "TimeFrame/interval_data.hpp"
9
#include "Entity/EntityTypes.hpp"
10

11
#include <map>
12
#include <optional>
13
#include <ranges>
14
#include <unordered_set>
15
#include <vector>
16

17

18
class EntityRegistry;
19

20

21
/**
22
 * @brief Structure holding a Point2D<float> and its associated EntityId
23
 */
24
struct PointEntry {
25
    Point2D<float> point;
26
    EntityId entity_id;
27
    
28
    PointEntry() = default;
29
    PointEntry(Point2D<float> p, EntityId id) : point(std::move(p)), entity_id(id) {}
1,320✔
30
};
31

32

33
/**
34
 * @brief PointData
35
 *
36
 * PointData is used for storing 2D point collections at specific time frames.
37
 * Each time frame can contain multiple points, making it suitable for tracking
38
 * multiple features or keypoints over time.
39
 *
40
 * For example, keypoints for multiple body points could be a PointData object
41
 */
42
class PointData : public ObserverData {
43
public:
44

45
    // ========== Constructors ==========
46
    /**
47
     * @brief Default constructor
48
     * 
49
     * This constructor creates an empty PointData with no data
50
     */
51
    PointData() = default;
302✔
52

53
    /**
54
     * @brief Constructor for PointData from a map of TimeFrameIndex to Point2D<float>
55
     * 
56
     * @param data Map of TimeFrameIndex to Point2D<float>
57
     */
58
    explicit PointData(std::map<TimeFrameIndex, Point2D<float>> const & data);
59

60
    /**
61
     * @brief Constructor for PointData from a map of TimeFrameIndex to vector of Point2D<float>
62
     * 
63
     * @param data Map of TimeFrameIndex to vector of Point2D<float>
64
     */
65
    explicit PointData(std::map<TimeFrameIndex, std::vector<Point2D<float>>> const & data);
66
    
67
    // ========== Setters ==========
68

69
    /**
70
     * @brief Clear the points at a specific time
71
     * 
72
     * @param time The time to clear the points at
73
     * @param notify If true, the observers will be notified
74
     * @return True if the points were cleared, false if the time did not exist
75
     */
76
    [[nodiscard]] bool clearAtTime(TimeFrameIndex time, bool notify = true);
77

78
    /**
79
     * @brief Clear the points at a specific time and index
80
     * 
81
     * @param time The time to clear the points at
82
     * @param index The index of the point to clear
83
     * @param notify If true, the observers will be notified
84
     * @return True if the point was cleared, false if the time or index did not exist
85
     */
86
    [[nodiscard]] bool clearAtTime(TimeFrameIndex time, size_t index, bool notify = true);
87

88
    /**
89
     * @brief Add a point at a specific time
90
     * 
91
     * This will add a single point at a specific time.
92
     * 
93
     * If the time does not exist, it will be created.
94
     * 
95
     * If the time already exists, the point will be added to the existing points.
96
     * 
97
     * @param time The time to add the point at
98
     * @param point The point to add
99
     * @param notify If true, the observers will be notified
100
     */
101
    void addAtTime(TimeFrameIndex time, Point2D<float> point, bool notify = true);
102

103
    /**
104
     * @brief Add multiple points at a specific time
105
     * 
106
     * This will add multiple points at a specific time.
107
     * 
108
     * If the time does not exist, it will be created.
109
     * 
110
     * If the time already exists, the points will be added to the existing points.
111
     * 
112
     * @param time The time to add the points at
113
     * @param points The points to add
114
     * @param notify If true, the observers will be notified
115
     */
116
    void addPointsAtTime(TimeFrameIndex time, std::vector<Point2D<float>> const & points, bool notify = true);
117

118
    /**
119
     * @brief Add a point entry at a specific time with a specific entity ID
120
     *
121
     * This method is used internally for move operations to preserve entity IDs.
122
     *
123
     * @param time The time to add the point at
124
     * @param point The point to add
125
     * @param entity_id The entity ID to assign to the point
126
     * @param notify If true, the observers will be notified
127
     */
128
    void addEntryAtTime(TimeFrameIndex time, Point2D<float> const & point, EntityId entity_id, bool notify = true);
129

130
    /**
131
     * @brief Overwrite a point at a specific time
132
     * 
133
     * This will overwrite a point at a specific time.
134
     * 
135
     * @param time The time to overwrite the point at
136
     * @param point The point to overwrite
137
     * @param notify If true, the observers will be notified
138
     */
139
    void overwritePointAtTime(TimeFrameIndex time, Point2D<float> point, bool notify = true);
140

141
    /**
142
     * @brief Overwrite multiple points at a specific time
143
     * 
144
     * This will overwrite multiple points at a specific time.
145
     * 
146
     * @param time The time to overwrite the points at
147
     * @param points The points to overwrite
148
     * @param notify If true, the observers will be notified
149
     */
150
    void overwritePointsAtTime(TimeFrameIndex time, std::vector<Point2D<float>> const & points, bool notify = true);
151

152
    /**
153
     * @brief Overwrite multiple points at multiple times
154
     * 
155
     * This will overwrite multiple points at multiple times.
156
     * 
157
     * @param times The times to overwrite the points at
158
     * @param points The points to overwrite
159
     * @param notify If true, the observers will be notified
160
     */
161
    void overwritePointsAtTimes(
162
            std::vector<TimeFrameIndex> const & times,
163
            std::vector<std::vector<Point2D<float>>> const & points,
164
            bool notify = true);
165

166
    // ========== Image Size ==========
167
    /*
168
    Image size is used to store the size of the image that the points belong to.
169
    */
170

171
    /**
172
     * @brief Get the image size
173
     * 
174
     * @return The image size
175
     */
176
    [[nodiscard]] ImageSize getImageSize() const { return _image_size; }
36✔
177

178
    /**
179
     * @brief Set the image size
180
     * 
181
     * @param image_size The image size
182
     */
183
    void setImageSize(ImageSize const & image_size) { _image_size = image_size; }
111✔
184

185
    /**
186
     * @brief Change the size of the canvas the points belong to
187
     *
188
     * This will scale all points in the data structure by the ratio of the
189
     * new size to the old size.
190
     *
191
     * @param image_size
192
     */
193
    void changeImageSize(ImageSize const & image_size);
194

195
    // ========== Getters ==========
196
    /**
197
     * @brief Get all times with data
198
     * 
199
     * Returns a view over the keys of the data map for zero-copy iteration.
200
     * 
201
     * @return A view of TimeFrameIndex keys
202
     */
203
    [[nodiscard]] auto getTimesWithData() const {
29✔
204
        return _data | std::views::keys;
29✔
205
    }
206

207
    /**
208
     * @brief Get the points at a specific time
209
     * 
210
     * If the time does not exist, an empty vector will be returned.
211
     * 
212
     * @param time The time to get the points at
213
     * @return A vector of Point2D<float>
214
     */
215
    [[nodiscard]] std::vector<Point2D<float>> const & getAtTime(TimeFrameIndex time) const;
216

217
    /**
218
     * @brief Get the points at a specific time with timeframe conversion
219
     * 
220
     * Converts the time index from the source timeframe to the target timeframe (this point data's timeframe)
221
     * and returns the points at the converted time. If the timeframes are the same, no conversion is performed.
222
     * If the converted time does not exist, an empty vector will be returned.
223
     * 
224
     * @param time The time index in the source timeframe
225
     * @param source_timeframe The timeframe that the time index is expressed in
226
     * @param target_timeframe The timeframe that this point data uses
227
     * @return A vector of Point2D<float> at the converted time
228
     */
229
    [[nodiscard]] std::vector<Point2D<float>> const & getAtTime(TimeFrameIndex time, 
230
                                                                TimeFrame const * source_timeframe,
231
                                                                TimeFrame const * target_timeframe) const;
232

233
    /**
234
     * @brief Get the maximum number of points at any time
235
     * 
236
     * @return The maximum number of points
237
     */
238
    [[nodiscard]] std::size_t getMaxPoints() const;
239

240
    /**
241
    * @brief Get all points with their associated times as a range
242
    *
243
    * @return A view of time-points pairs for all times
244
    */
245
    [[nodiscard]] auto GetAllPointsAsRange() const {
104✔
246
        struct TimePointsPair {
247
            TimeFrameIndex time;
248
            std::vector<Point2D<float>> points; // Copy for backward compatibility with entry storage
249
        };
250

251
        return _data | std::views::transform([](auto const & pair) {
208✔
252
                   std::vector<Point2D<float>> points;
188✔
253
                   points.reserve(pair.second.size());
188✔
254
                   for (auto const & entry : pair.second) {
557✔
255
                       points.push_back(entry.point);
369✔
256
                   }
257
                   return TimePointsPair{pair.first, std::move(points)};
376✔
258
               });
688✔
259
    }
260

261
    /**
262
    * @brief Get all point entries with their associated times as a zero-copy range
263
    *
264
    * This method provides zero-copy access to the underlying PointEntry data structure,
265
    * which contains both Point2D<float> and EntityId information.
266
    *
267
    * @return A view of time-point entries pairs for all times
268
    */
269
    [[nodiscard]] auto GetAllPointEntriesAsRange() const {
40✔
270
        struct TimePointEntriesPair {
271
            TimeFrameIndex time;
272
            std::vector<PointEntry> const & entries;
273
        };
274

275
        return _data | std::views::transform([](auto const & pair) {
80✔
276
                   return TimePointEntriesPair{pair.first, pair.second};
77✔
277
               });
120✔
278
    }
279

280
    /**
281
    * @brief Get points with their associated times as a range within a TimeFrameInterval
282
    *
283
    * Returns a filtered view of time-points pairs for times within the specified interval [start, end] (inclusive).
284
    *
285
    * @param interval The TimeFrameInterval specifying the range [start, end] (inclusive)
286
    * @return A view of time-points pairs for times within the specified interval
287
    */
288
    [[nodiscard]] auto GetPointsInRange(TimeFrameInterval const & interval) const {
31✔
289
        struct TimePointsPair {
290
            TimeFrameIndex time;
291
            std::vector<Point2D<float>> points; // Copy for backward compatibility
292
        };
293

294
        return _data 
31✔
295
            | std::views::filter([interval](auto const & pair) {
62✔
296
                return pair.first >= interval.start && pair.first <= interval.end;
161✔
297
              })
298
            | std::views::transform([](auto const & pair) {
62✔
299
                std::vector<Point2D<float>> points;
67✔
300
                points.reserve(pair.second.size());
67✔
301
                for (auto const & entry : pair.second) {
144✔
302
                    points.push_back(entry.point);
77✔
303
                }
304
                return TimePointsPair{pair.first, std::move(points)};
134✔
305
              });
258✔
306
    }
307

308
    /**
309
    * @brief Get points with their associated times as a range within a TimeFrameInterval with timeframe conversion
310
    *
311
    * Converts the time range from the source timeframe to the target timeframe (this point data's timeframe)
312
    * and returns a filtered view of time-points pairs for times within the converted interval range.
313
    * If the timeframes are the same, no conversion is performed.
314
    *
315
    * @param interval The TimeFrameInterval in the source timeframe specifying the range [start, end] (inclusive)
316
    * @param source_timeframe The timeframe that the interval is expressed in
317
    * @param point_timeframe The timeframe that this point data uses
318
    * @return A view of time-points pairs for times within the converted interval range
319
    */
320
    [[nodiscard]] auto GetPointsInRange(TimeFrameInterval const & interval,
25✔
321
                                        TimeFrame const * source_timeframe,
322
                                        TimeFrame const * point_timeframe) const {
323
        // If the timeframes are the same object, no conversion is needed
324
        if (source_timeframe == point_timeframe) {
25✔
325
            return GetPointsInRange(interval);
21✔
326
        }
327

328
        // If either timeframe is null, fall back to original behavior
329
        if (!source_timeframe || !point_timeframe) {
4✔
UNCOV
330
            return GetPointsInRange(interval);
×
331
        }
332

333
        // Convert the time range from source timeframe to target timeframe
334
        // 1. Get the time values from the source timeframe
335
        auto start_time_value = source_timeframe->getTimeAtIndex(interval.start);
4✔
336
        auto end_time_value = source_timeframe->getTimeAtIndex(interval.end);
4✔
337

338
        // 2. Convert those time values to indices in the point timeframe
339
        auto target_start_index = point_timeframe->getIndexAtTime(static_cast<float>(start_time_value), false);
4✔
340
        auto target_end_index = point_timeframe->getIndexAtTime(static_cast<float>(end_time_value));
4✔
341

342
        // 3. Create converted interval and use the original function
343
        TimeFrameInterval target_interval{target_start_index, target_end_index};
4✔
344
        return GetPointsInRange(target_interval);
4✔
345
    }
346

347
    // ======= Move and Copy ==========
348

349
    /**
350
     * @brief Copy points from this PointData to another PointData for a time interval
351
     * 
352
     * Copies all points within the specified time interval [start, end] (inclusive)
353
     * to the target PointData. If points already exist at target times, the copied points
354
     * are added to the existing points.
355
     * 
356
     * @param target The target PointData to copy points to
357
     * @param interval The time interval to copy points from (inclusive)
358
     * @param notify If true, the target will notify its observers after the operation
359
     * @return The number of points actually copied
360
     */
361
    std::size_t copyTo(PointData& target, TimeFrameInterval const & interval, bool notify = true) const;
362

363
    /**
364
     * @brief Copy points from this PointData to another PointData for specific times
365
     * 
366
     * Copies all points at the specified times to the target PointData.
367
     * If points already exist at target times, the copied points are added to the existing points.
368
     * 
369
     * @param target The target PointData to copy points to
370
     * @param times Vector of specific times to copy (does not need to be sorted)
371
     * @param notify If true, the target will notify its observers after the operation
372
     * @return The number of points actually copied
373
     */
374
    std::size_t copyTo(PointData& target, std::vector<TimeFrameIndex> const& times, bool notify = true) const;
375

376
    /**
377
     * @brief Move points from this PointData to another PointData for a time interval
378
     * 
379
     * Moves all points within the specified time interval [start, end] (inclusive)
380
     * to the target PointData. Points are copied to target then removed from source.
381
     * If points already exist at target times, the moved points are added to the existing points.
382
     * 
383
     * @param target The target PointData to move points to
384
     * @param interval The time interval to move points from (inclusive)
385
     * @param notify If true, both source and target will notify their observers after the operation
386
     * @return The number of points actually moved
387
     */
388
    std::size_t moveTo(PointData& target, TimeFrameInterval const & interval, bool notify = true);
389

390
    /**
391
     * @brief Move points from this PointData to another PointData for specific times
392
     * 
393
     * Moves all points at the specified times to the target PointData.
394
     * Points are copied to target then removed from source.
395
     * If points already exist at target times, the moved points are added to the existing points.
396
     * 
397
     * @param target The target PointData to move points to
398
     * @param times Vector of specific times to move (does not need to be sorted)
399
     * @param notify If true, both source and target will notify their observers after the operation
400
     * @return The number of points actually moved
401
     */
402
    std::size_t moveTo(PointData& target, std::vector<TimeFrameIndex> const& times, bool notify = true);
403

404
    /**
405
     * @brief Copy points with specific EntityIds to another PointData
406
     *
407
     * Copies all points that match the given EntityIds to the target PointData.
408
     * The copied points will get new EntityIds in the target.
409
     *
410
     * @param target The target PointData to copy points to
411
     * @param entity_ids Vector of EntityIds to copy
412
     * @param notify If true, the target will notify its observers after the operation
413
     * @return The number of points actually copied
414
     */
415
     std::size_t copyByEntityIds(PointData & target, std::unordered_set<EntityId> const & entity_ids, bool notify = true);
416

417
    /**
418
     * @brief Move points with specific EntityIds to another PointData
419
     *
420
     * Moves all points that match the given EntityIds to the target PointData.
421
     * The moved points will get the same EntityIds in the target and be removed from source.
422
     *
423
     * @param target The target PointData to move points to
424
     * @param entity_ids Vector of EntityIds to move
425
     * @param notify If true, both source and target will notify their observers after the operation
426
     * @return The number of points actually moved
427
     */
428
    std::size_t moveByEntityIds(PointData & target, std::unordered_set<EntityId> const & entity_ids, bool notify = true);
429

430
    // ========== Time Frame ==========
431

432
    /**
433
     * @brief Set the time frame
434
     * 
435
     * @param time_frame The time frame to set
436
     */
437
    void setTimeFrame(std::shared_ptr<TimeFrame> time_frame) { _time_frame = time_frame; }
168✔
438

439
    /**
440
     * @brief Set identity context for automatic EntityId maintenance.
441
     * @pre registry != nullptr
442
     */
443
    void setIdentityContext(std::string const & data_key, EntityRegistry * registry);
444

445
    /**
446
     * @brief Rebuild EntityIds for all points using the current identity context.
447
     * @pre Identity context has been set via setIdentityContext
448
     */
449
    void rebuildAllEntityIds();
450

451
    /**
452
     * @brief Get EntityIds aligned with points at a specific time.
453
     */
454
    [[nodiscard]] std::vector<EntityId> const & getEntityIdsAtTime(TimeFrameIndex time) const;
455

456
    /**
457
     * @brief Get flattened EntityIds for all points across all times.
458
     */
459
    [[nodiscard]] std::vector<EntityId> getAllEntityIds() const;
460

461
    // ========== Entity Lookup Methods ==========
462

463
    /**
464
     * @brief Find the point data associated with a specific EntityId.
465
     * 
466
     * This method provides reverse lookup from EntityId to the actual point data,
467
     * supporting group-based visualization workflows.
468
     * 
469
     * @param entity_id The EntityId to look up
470
     * @return Optional containing the point data if found, std::nullopt otherwise
471
     */
472
    [[nodiscard]] std::optional<Point2D<float>> getPointByEntityId(EntityId entity_id) const;
473

474
    /**
475
     * @brief Find the time frame and local index for a specific EntityId.
476
     * 
477
     * Returns the time frame and local point index (within that time frame)
478
     * associated with the given EntityId.
479
     * 
480
     * @param entity_id The EntityId to look up
481
     * @return Optional containing {time, local_index} if found, std::nullopt otherwise
482
     */
483
    [[nodiscard]] std::optional<std::pair<TimeFrameIndex, int>> getTimeAndIndexByEntityId(EntityId entity_id) const;
484

485
    /**
486
     * @brief Get all points that match the given EntityIds.
487
     * 
488
     * This method is optimized for batch lookup of multiple EntityIds,
489
     * useful for group-based operations.
490
     * 
491
     * @param entity_ids Vector of EntityIds to look up
492
     * @return Vector of pairs containing {EntityId, Point2D<float>} for found entities
493
     */
494
    [[nodiscard]] std::vector<std::pair<EntityId, Point2D<float>>> getPointsByEntityIds(std::vector<EntityId> const & entity_ids) const;
495

496
    /**
497
     * @brief Get time frame information for multiple EntityIds.
498
     * 
499
     * Returns time frame and local index information for a batch of EntityIds.
500
     * 
501
     * @param entity_ids Vector of EntityIds to look up
502
     * @return Vector of tuples containing {EntityId, time, local_index} for found entities
503
     */
504
    [[nodiscard]] std::vector<std::tuple<EntityId, TimeFrameIndex, int>> getTimeInfoByEntityIds(std::vector<EntityId> const & entity_ids) const;
505

506
protected:
507
private:
508
    std::map<TimeFrameIndex, std::vector<PointEntry>> _data;
509
    std::vector<Point2D<float>> _empty{};
510
    std::vector<EntityId> _empty_entity_ids{};
511
    mutable std::vector<Point2D<float>> _temp_points{};
512
    mutable std::vector<EntityId> _temp_entity_ids{};
513
    ImageSize _image_size;
514
    std::shared_ptr<TimeFrame> _time_frame {nullptr};
515

516
    // Identity management
517
    std::string _identity_data_key;
518
    EntityRegistry * _identity_registry {nullptr};
519
    // Entity IDs are stored within PointEntry alongside points in _data
520
};
521

522
#endif// POINT_DATA_HPP
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