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

paulmthompson / WhiskerToolbox / 18246927847

04 Oct 2025 04:44PM UTC coverage: 71.826% (+0.6%) from 71.188%
18246927847

push

github

paulmthompson
refactor out media producer consumer pipeline

0 of 120 new or added lines in 2 files covered. (0.0%)

646 existing lines in 14 files now uncovered.

48895 of 68074 relevant lines covered (71.83%)

1193.51 hits per line

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

73.22
/src/DataManager/Lines/Line_Data.cpp
1
#include "Line_Data.hpp"
2

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

7
#include <cmath>
8
#include <iostream>
9
#include <ranges>
10

11
// ========== Constructors ==========
12

13
LineData::LineData(std::map<TimeFrameIndex, std::vector<Line2D>> const & data) {
41✔
14
    // Convert old format to new format
15
    for (auto const & [time, lines] : data) {
96✔
16
        _data[time].reserve(lines.size());
55✔
17
        for (auto const & line : lines) {
118✔
18
            _data[time].emplace_back(line, 0); // EntityId will be 0 initially
63✔
19
        }
20
    }
21
}
41✔
22

23
// ========== Setters ==========
24

25
bool LineData::clearAtTime(TimeFrameIndex const time, bool notify) {
8✔
26
    auto it = _data.find(time);
8✔
27
    if (it != _data.end()) {
8✔
28
        _data.erase(it);
8✔
29
        if (notify) {
8✔
30
            notifyObservers();
1✔
31
        }
32
        return true;
8✔
33
    }
34
    return false;
×
35
}
36

37
bool LineData::clearAtTime(TimeFrameIndex const time, int const line_id, bool notify) {
×
38
    auto it = _data.find(time);
×
39
    if (it != _data.end() && static_cast<size_t>(line_id) < it->second.size()) {
×
40
        it->second.erase(it->second.begin() + static_cast<long int>(line_id));
×
41
        if (it->second.empty()) {
×
42
            _data.erase(it);
×
43
        }
44
        if (notify) {
×
45
            notifyObservers();
×
46
        }
47
        return true;
×
48
    }
49
    return false;
×
50
}
51

52
void LineData::addAtTime(TimeFrameIndex const time, std::vector<float> const & x, std::vector<float> const & y, bool notify) {
622✔
53
    Line2D new_line;
622✔
54
    for (size_t i = 0; i < std::min(x.size(), y.size()); ++i) {
6,818✔
55
        new_line.push_back(Point2D<float>{x[i], y[i]});
6,196✔
56
    }
57
    
58
    int const local_index = static_cast<int>(_data[time].size());
622✔
59
    EntityId entity_id = 0;
622✔
60
    if (_identity_registry) {
622✔
61
        entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index);
32✔
62
    }
63
    
64
    _data[time].emplace_back(std::move(new_line), entity_id);
622✔
65

66
    if (notify) {
622✔
67
        notifyObservers();
242✔
68
    }
69
}
1,244✔
70

71
void LineData::addAtTime(TimeFrameIndex const time, std::vector<Point2D<float>> const & line, bool notify) {
102✔
72
    addAtTime(time, Line2D(line), notify);
102✔
73
}
102✔
74

75
void LineData::addAtTime(TimeFrameIndex const time, Line2D const & line, bool notify) {
2,485✔
76
    int const local_index = static_cast<int>(_data[time].size());
2,485✔
77
    EntityId entity_id = 0;
2,485✔
78
    if (_identity_registry) {
2,485✔
79
        entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index);
2,006✔
80
    }
81
    
82
    _data[time].emplace_back(line, entity_id);
2,485✔
83

84
    if (notify) {
2,485✔
85
        notifyObservers();
351✔
86
    }
87
}
2,485✔
88

89
void LineData::addPointToLine(TimeFrameIndex const time, int const line_id, Point2D<float> point, bool notify) {
×
90
    if (static_cast<size_t>(line_id) < _data[time].size()) {
×
91
        _data[time][static_cast<size_t>(line_id)].line.push_back(point);
×
92
    } else {
93
        std::cerr << "LineData::addPointToLine: line_id out of range" << std::endl;
×
94
        EntityId entity_id = 0;
×
95
        if (_identity_registry) {
×
96
            int const local_index = static_cast<int>(_data[time].size());
×
97
            entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index);
×
98
        }
99
        _data[time].emplace_back(Line2D{point}, entity_id);
×
100
    }
101

102
    if (notify) {
×
103
        notifyObservers();
×
104
    }
105
}
×
106

107
void LineData::addPointToLineInterpolate(TimeFrameIndex const time, int const line_id, Point2D<float> point, bool notify) {
×
108
    if (static_cast<size_t>(line_id) >= _data[time].size()) {
×
109
        std::cerr << "LineData::addPointToLineInterpolate: line_id out of range" << std::endl;
×
110
        EntityId entity_id = 0;
×
111
        if (_identity_registry) {
×
112
            int const local_index = static_cast<int>(_data[time].size());
×
113
            entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index);
×
114
        }
115
        _data[time].emplace_back(Line2D{}, entity_id);
×
116
    }
117

118
    Line2D & line = _data[time][static_cast<size_t>(line_id)].line;
×
119
    if (!line.empty()) {
×
120
        Point2D<float> const last_point = line.back();
×
121
        float const distance = std::sqrt(std::pow(point.x - last_point.x, 2.0f) + std::pow(point.y - last_point.y, 2.0f));
×
122
        int const n = static_cast<int>(distance / 2.0f);
×
123
        for (int i = 1; i <= n; ++i) {
×
124
            float const t = static_cast<float>(i) / static_cast<float>(n + 1);
×
125
            float const interp_x = last_point.x + t * (point.x - last_point.x);
×
126
            float const interp_y = last_point.y + t * (point.y - last_point.y);
×
127
            line.push_back(Point2D<float>{interp_x, interp_y});
×
128
        }
129
    }
130
    line.push_back(point);
×
131
    // Note: smooth_line function needs to be implemented or included
132
    // smooth_line(line);
133

134
    if (notify) {
×
135
        notifyObservers();
×
136
    }
137
}
×
138

139
void LineData::addLineEntryAtTime(TimeFrameIndex const time, Line2D const & line, EntityId entity_id, bool notify) {
6✔
140
    _data[time].emplace_back(line, entity_id);
6✔
141

142
    if (notify) {
6✔
UNCOV
143
        notifyObservers();
×
144
    }
145
}
6✔
146

147
// ========== Getters ==========
148

149
std::vector<Line2D> const & LineData::getAtTime(TimeFrameIndex const time) const {
445✔
150
    // This method needs to return a reference to a vector of Line2D objects
151
    // Since we now store LineEntry objects, we need to create a temporary vector
152
    // We'll use a member variable to store the converted data to maintain reference stability
153
    
154
    auto it = _data.find(time);
445✔
155
    if (it == _data.end()) {
445✔
156
        return _empty;
42✔
157
    }
158
    
159
    // Use a mutable member variable to store the converted lines
160
    // This is not thread-safe but maintains API compatibility
161
    _temp_lines.clear();
403✔
162
    _temp_lines.reserve(it->second.size());
403✔
163
    for (auto const & entry : it->second) {
962✔
164
        _temp_lines.push_back(entry.line);
559✔
165
    }
166
    
167
    return _temp_lines;
403✔
168
}
169

170
std::vector<Line2D> const & LineData::getAtTime(TimeFrameIndex const time, 
205✔
171
                                                TimeFrame const * source_timeframe,
172
                                                TimeFrame const * line_timeframe) const {
173
    // Convert time if needed
174
    TimeFrameIndex converted_time = time;
205✔
175
    if (source_timeframe && line_timeframe && source_timeframe != line_timeframe) {
205✔
UNCOV
176
        auto time_value = source_timeframe->getTimeAtIndex(time);
×
UNCOV
177
        converted_time = line_timeframe->getIndexAtTime(static_cast<float>(time_value));
×
178
    }
179
    
180
    return getAtTime(converted_time);
410✔
181
}
182

183
std::vector<EntityId> const & LineData::getEntityIdsAtTime(TimeFrameIndex const time) const {
1,304✔
184
    // Similar to getAtTime, we need to create a temporary vector
185
    auto it = _data.find(time);
1,304✔
186
    if (it == _data.end()) {
1,304✔
187
        return _empty_entity_ids;
10✔
188
    }
189
    
190
    _temp_entity_ids.clear();
1,294✔
191
    _temp_entity_ids.reserve(it->second.size());
1,294✔
192
    for (auto const & entry : it->second) {
3,823✔
193
        _temp_entity_ids.push_back(entry.entity_id);
2,529✔
194
    }
195
    
196
    return _temp_entity_ids;
1,294✔
197
}
198

199
std::vector<EntityId> const & LineData::getEntityIdsAtTime(TimeFrameIndex const time,
137✔
200
                                                           TimeFrame const * source_timeframe,
201
                                                           TimeFrame const * line_timeframe) const {
202
    // Convert time if needed
203
    TimeFrameIndex converted_time = time;
137✔
204
    if (source_timeframe && line_timeframe && source_timeframe != line_timeframe) {
137✔
UNCOV
205
        auto time_value = source_timeframe->getTimeAtIndex(time);
×
UNCOV
206
        converted_time = line_timeframe->getIndexAtTime(static_cast<float>(time_value));
×
207
    }
208
    
209
    return getEntityIdsAtTime(converted_time);
274✔
210
}
211

212
std::vector<EntityId> LineData::getAllEntityIds() const {
50✔
213
    std::vector<EntityId> out;
50✔
214
    for (auto const & [t, entries] : _data) {
1,543✔
215
        (void)t;
216
        for (auto const & entry : entries) {
4,448✔
217
            out.push_back(entry.entity_id);
2,955✔
218
        }
219
    }
220
    return out;
50✔
UNCOV
221
}
×
222

223
// ========== Entity Lookup Methods ==========
224

225
std::optional<Line2D> LineData::getLineByEntityId(EntityId entity_id) const {
4,042✔
226
    if (!_identity_registry) {
4,042✔
227
        return std::nullopt;
7✔
228
    }
229
    
230
    auto descriptor = _identity_registry->get(entity_id);
4,035✔
231
    if (!descriptor || descriptor->kind != EntityKind::LineEntity || descriptor->data_key != _identity_data_key) {
4,035✔
232
        return std::nullopt;
×
233
    }
234
    
235
    TimeFrameIndex const time{descriptor->time_value};
4,035✔
236
    int const local_index = descriptor->local_index;
4,035✔
237
    
238
    auto time_it = _data.find(time);
4,035✔
239
    if (time_it == _data.end()) {
4,035✔
UNCOV
240
        return std::nullopt;
×
241
    }
242
    
243
    if (local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
4,035✔
244
        return std::nullopt;
×
245
    }
246
    
247
    return time_it->second[static_cast<size_t>(local_index)].line;
4,035✔
248
}
4,035✔
249

250
std::optional<std::pair<TimeFrameIndex, int>> LineData::getTimeAndIndexByEntityId(EntityId entity_id) const {
176✔
251
    if (!_identity_registry) {
176✔
252
        return std::nullopt;
7✔
253
    }
254
    
255
    auto descriptor = _identity_registry->get(entity_id);
169✔
256
    if (!descriptor || descriptor->kind != EntityKind::LineEntity || descriptor->data_key != _identity_data_key) {
169✔
UNCOV
257
        return std::nullopt;
×
258
    }
259
    
260
    TimeFrameIndex const time{descriptor->time_value};
169✔
261
    int const local_index = descriptor->local_index;
169✔
262
    
263
    // Verify the time and index are valid
264
    auto time_it = _data.find(time);
169✔
265
    if (time_it == _data.end() || local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
169✔
UNCOV
266
        return std::nullopt;
×
267
    }
268
    
269
    return std::make_pair(time, local_index);
169✔
270
}
169✔
271

272
std::vector<std::pair<EntityId, Line2D>> LineData::getLinesByEntityIds(std::vector<EntityId> const & entity_ids) const {
5✔
273
    std::vector<std::pair<EntityId, Line2D>> results;
5✔
274
    results.reserve(entity_ids.size());
5✔
275
    
276
    for (EntityId const entity_id : entity_ids) {
17✔
277
        auto line = getLineByEntityId(entity_id);
12✔
278
        if (line.has_value()) {
12✔
279
            results.emplace_back(entity_id, std::move(line.value()));
7✔
280
        }
281
    }
12✔
282
    
283
    return results;
5✔
UNCOV
284
}
×
285

286
std::vector<std::tuple<EntityId, TimeFrameIndex, int>> LineData::getTimeInfoByEntityIds(std::vector<EntityId> const & entity_ids) const {
4✔
287
    std::vector<std::tuple<EntityId, TimeFrameIndex, int>> results;
4✔
288
    results.reserve(entity_ids.size());
4✔
289
    
290
    for (EntityId const entity_id : entity_ids) {
13✔
291
        auto time_info = getTimeAndIndexByEntityId(entity_id);
9✔
292
        if (time_info.has_value()) {
9✔
293
            results.emplace_back(entity_id, time_info->first, time_info->second);
4✔
294
        }
295
    }
296
    
297
    return results;
4✔
298
}
×
299

300
// ========== Image Size ==========
301

302
void LineData::changeImageSize(ImageSize const & image_size)
×
303
{
UNCOV
304
    if (_image_size.width == -1 || _image_size.height == -1) {
×
305
        std::cout << "No size set for current image. "
306
                  << " Please set a valid image size before trying to scale" << std::endl;
×
307
    }
308

309
    if (_image_size.width == image_size.width && _image_size.height == image_size.height) {
×
310
        std::cout << "Image size is the same. No need to scale" << std::endl;
×
311
        return;
×
312
    }
313

UNCOV
314
    float const scale_x = static_cast<float>(image_size.width) / static_cast<float>(_image_size.width);
×
UNCOV
315
    float const scale_y = static_cast<float>(image_size.height) / static_cast<float>(_image_size.height);
×
316

317
    for (auto & [time, entries] : _data) {
×
UNCOV
318
        for (auto & entry : entries) {
×
UNCOV
319
            for (auto & point : entry.line) {
×
UNCOV
320
                point.x *= scale_x;
×
UNCOV
321
                point.y *= scale_y;
×
322
            }
323
        }
324
    }
UNCOV
325
    _image_size = image_size;
×
326
}
327

328
void LineData::setIdentityContext(std::string const & data_key, EntityRegistry * registry) {
333✔
329
    _identity_data_key = data_key;
333✔
330
    _identity_registry = registry;
333✔
331
}
333✔
332

333
void LineData::rebuildAllEntityIds() {
333✔
334
    if (!_identity_registry) {
333✔
UNCOV
335
        for (auto & [t, entries] : _data) {
×
UNCOV
336
            for (auto & entry : entries) {
×
UNCOV
337
                entry.entity_id = 0;
×
338
            }
339
        }
UNCOV
340
        return;
×
341
    }
342
    
343
    for (auto & [t, entries] : _data) {
1,263✔
344
        for (int i = 0; i < static_cast<int>(entries.size()); ++i) {
2,149✔
345
            entries[static_cast<size_t>(i)].entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, t, i);
1,219✔
346
        }
347
    }
348
}
349

350
// ========== Copy and Move ==========
351

352
std::size_t LineData::copyTo(LineData& target, TimeFrameInterval const & interval, bool notify) const {
8✔
353
    if (interval.start > interval.end) {
8✔
354
        std::cerr << "LineData::copyTo: interval start (" << interval.start.getValue() 
1✔
355
                  << ") must be <= interval end (" << interval.end.getValue() << ")" << std::endl;
1✔
356
        return 0;
1✔
357
    }
358

359
    std::size_t total_lines_copied = 0;
7✔
360

361
    // Iterate through all times in the source data within the interval
362
    for (auto const & [time, entries] : _data) {
27✔
363
        if (time >= interval.start && time <= interval.end && !entries.empty()) {
20✔
364
            for (auto const& entry : entries) {
21✔
365
                target.addAtTime(time, entry.line, false); // Don't notify for each operation
13✔
366
                total_lines_copied++;
13✔
367
            }
368
        }
369
    }
370

371
    // Notify observer only once at the end if requested
372
    if (notify && total_lines_copied > 0) {
7✔
373
        target.notifyObservers();
5✔
374
    }
375

376
    return total_lines_copied;
7✔
377
}
378

379
std::size_t LineData::copyTo(LineData& target, std::vector<TimeFrameIndex> const& times, bool notify) const {
1✔
380
    std::size_t total_lines_copied = 0;
1✔
381

382
    // Copy lines for each specified time
383
    for (TimeFrameIndex const time : times) {
3✔
384
        auto it = _data.find(time);
2✔
385
        if (it != _data.end() && !it->second.empty()) {
2✔
386
            for (auto const& entry : it->second) {
5✔
387
                target.addAtTime(time, entry.line, false); // Don't notify for each operation
3✔
388
                total_lines_copied++;
3✔
389
            }
390
        }
391
    }
392

393
    // Notify observer only once at the end if requested
394
    if (notify && total_lines_copied > 0) {
1✔
395
        target.notifyObservers();
1✔
396
    }
397

398
    return total_lines_copied;
1✔
399
}
400

401
std::size_t LineData::moveTo(LineData& target, TimeFrameInterval const & interval, bool notify) {
4✔
402
    if (interval.start > interval.end) {
4✔
UNCOV
403
        std::cerr << "LineData::moveTo: interval start (" << interval.start.getValue() 
×
404
                  << ") must be <= interval end (" << interval.end.getValue() << ")" << std::endl;
×
405
        return 0;
×
406
    }
407

408
    std::size_t total_lines_moved = 0;
4✔
409
    std::vector<TimeFrameIndex> times_to_clear;
4✔
410

411
    // First, copy all lines in the interval to target
412
    for (auto const & [time, entries] : _data) {
13✔
413
        if (time >= interval.start && time <= interval.end && !entries.empty()) {
9✔
414
            for (auto const& entry : entries) {
12✔
415
                target.addAtTime(time, entry.line, false); // Don't notify for each operation
7✔
416
                total_lines_moved++;
7✔
417
            }
418
            times_to_clear.push_back(time);
5✔
419
        }
420
    }
421

422
    // Then, clear all the times from source
423
    for (TimeFrameIndex const time : times_to_clear) {
9✔
424
        (void)clearAtTime(time, false); // Don't notify for each operation
5✔
425
    }
426

427
    // Notify observers only once at the end if requested
428
    if (notify && total_lines_moved > 0) {
4✔
429
        target.notifyObservers();
3✔
430
        notifyObservers();
3✔
431
    }
432

433
    return total_lines_moved;
4✔
434
}
4✔
435

436
std::size_t LineData::moveTo(LineData& target, std::vector<TimeFrameIndex> const & times, bool notify) {
2✔
437
    std::size_t total_lines_moved = 0;
2✔
438
    std::vector<TimeFrameIndex> times_to_clear;
2✔
439

440
    // First, copy lines for each specified time to target
441
    for (TimeFrameIndex const time : times) {
6✔
442
        auto it = _data.find(time);
4✔
443
        if (it != _data.end() && !it->second.empty()) {
4✔
444
            for (auto const& entry : it->second) {
4✔
445
                target.addAtTime(time, entry.line, false); // Don't notify for each operation
2✔
446
                total_lines_moved++;
2✔
447
            }
448
            times_to_clear.push_back(time);
2✔
449
        }
450
    }
451

452
    // Then, clear all the times from source
453
    for (TimeFrameIndex const time : times_to_clear) {
4✔
454
        (void)clearAtTime(time, false); // Don't notify for each operation
2✔
455
    }
456

457
    // Notify observers only once at the end if requested
458
    if (notify && total_lines_moved > 0) {
2✔
459
        target.notifyObservers();
1✔
460
        notifyObservers();
1✔
461
    }
462

463
    return total_lines_moved;
2✔
464
}
2✔
465

466
std::size_t LineData::copyLinesByEntityIds(LineData & target, std::vector<EntityId> const & entity_ids, bool notify) {
5✔
467
    std::size_t total_lines_copied = 0;
5✔
468
    
469
    // Iterate through all data to find matching EntityIds
470
    for (auto const & [time, entries] : _data) {
17✔
471
        for (auto const & entry : entries) {
28✔
472
            // Check if this entry's EntityId is in the list to copy
473
            if (std::ranges::find(entity_ids, entry.entity_id) != entity_ids.end()) {
16✔
474
                target.addAtTime(time, entry.line, false); // Don't notify for each operation
6✔
475
                total_lines_copied++;
6✔
476
            }
477
        }
478
    }
479
    
480
    // Notify observer only once at the end if requested
481
    if (notify && total_lines_copied > 0) {
5✔
482
        target.notifyObservers();
3✔
483
    }
484
    
485
    return total_lines_copied;
5✔
486
}
487

488
std::size_t LineData::moveLinesByEntityIds(LineData & target, std::vector<EntityId> const & entity_ids, bool notify) {
4✔
489
    std::size_t total_lines_moved = 0;
4✔
490
    std::vector<std::pair<TimeFrameIndex, size_t>> entries_to_remove;
4✔
491
    
492
    // First, copy all matching lines to target and collect removal information
493
    for (auto const & [time, entries] : _data) {
16✔
494
        for (size_t i = 0; i < entries.size(); ++i) {
28✔
495
            auto const & entry = entries[i];
16✔
496
            // Check if this entry's EntityId is in the list to move
497
            if (std::ranges::find(entity_ids, entry.entity_id) != entity_ids.end()) {
16✔
498
                // For move operations, preserve the original entity ID
499
                target.addLineEntryAtTime(time, entry.line, entry.entity_id, false);
6✔
500
                entries_to_remove.emplace_back(time, i);
6✔
501
                total_lines_moved++;
6✔
502
            }
503
        }
504
    }
505
    
506
    // Then, remove the moved entries from source (in reverse order to maintain indices)
507
    std::ranges::sort(entries_to_remove, 
4✔
508
                      [](auto const & a, auto const & b) {
3✔
509
                          if (a.first != b.first) return a.first > b.first; // Sort by time descending
3✔
510
                          return a.second > b.second; // Then by index descending
2✔
511
                      });
512
    
513
    for (auto const & [time, index] : entries_to_remove) {
10✔
514
        auto it = _data.find(time);
6✔
515
        if (it != _data.end() && index < it->second.size()) {
6✔
516
            it->second.erase(it->second.begin() + static_cast<long>(index));
6✔
517
            if (it->second.empty()) {
6✔
518
                _data.erase(it);
3✔
519
            }
520
        }
521
    }
522
    
523
    // Notify observers only once at the end if requested
524
    if (notify && total_lines_moved > 0) {
4✔
525
        target.notifyObservers();
3✔
526
        notifyObservers();
3✔
527
    }
528
    
529
    return total_lines_moved;
4✔
530
}
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