• 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

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

3
#include "CoreGeometry/points.hpp"
4
#include "Entity/EntityRegistry.hpp"
5
#include "utils/map_timeseries.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
LineData::LineData(LineData && other) noexcept
×
24
    : ObserverData(std::move(other)),
×
25
      _data(std::move(other._data)),
×
26
      _image_size(other._image_size),
×
27
      _time_frame(std::move(other._time_frame)),
×
28
      _identity_data_key(std::move(other._identity_data_key)),
×
29
      _identity_registry(other._identity_registry) {
×
UNCOV
30
    other._identity_registry = nullptr;
×
31
}
×
32

UNCOV
33
LineData & LineData::operator=(LineData && other) noexcept {
×
34
    if (this != &other) {
×
UNCOV
35
        ObserverData::operator=(std::move(other));
×
36
        _data = std::move(other._data);
×
37
        _image_size = other._image_size;
×
38
        _time_frame = std::move(other._time_frame);
×
39
        _identity_data_key = std::move(other._identity_data_key);
×
40
        _identity_registry = other._identity_registry;
×
41
        other._identity_registry = nullptr;
×
42
    }
43
    return *this;
×
44
}
45

46
// ========== Setters ==========
47

48
bool LineData::clearAtTime(TimeFrameIndex const time, bool notify) {
1✔
49
    auto it = _data.find(time);
1✔
50
    if (it != _data.end()) {
1✔
51
        _data.erase(it);
1✔
52
        if (notify) {
1✔
53
            notifyObservers();
1✔
54
        }
55
        return true;
1✔
56
    }
UNCOV
57
    return false;
×
58
}
59

UNCOV
60
bool LineData::clearAtTime(TimeFrameIndex const time, int const line_id, bool notify) {
×
UNCOV
61
    auto it = _data.find(time);
×
62
    if (it != _data.end() && static_cast<size_t>(line_id) < it->second.size()) {
×
63
        it->second.erase(it->second.begin() + static_cast<long int>(line_id));
×
64
        if (it->second.empty()) {
×
65
            _data.erase(it);
×
66
        }
67
        if (notify) {
×
UNCOV
68
            notifyObservers();
×
69
        }
70
        return true;
×
71
    }
72
    return false;
×
73
}
74

75
void LineData::addAtTime(TimeFrameIndex const time, std::vector<float> const & x, std::vector<float> const & y, bool notify) {
624✔
76
    Line2D new_line;
624✔
77
    for (size_t i = 0; i < std::min(x.size(), y.size()); ++i) {
6,824✔
78
        new_line.push_back(Point2D<float>{x[i], y[i]});
6,200✔
79
    }
80

81
    int const local_index = static_cast<int>(_data[time].size());
624✔
82
    EntityId entity_id = 0;
624✔
83
    if (_identity_registry) {
624✔
84
        entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index);
32✔
85
    }
86

87
    _data[time].emplace_back(std::move(new_line), entity_id);
624✔
88

89
    if (notify) {
624✔
90
        notifyObservers();
244✔
91
    }
92
}
1,248✔
93

94
void LineData::addAtTime(TimeFrameIndex const time, std::vector<Point2D<float>> const & line, bool notify) {
102✔
95
    addAtTime(time, Line2D(line), notify);
102✔
96
}
102✔
97

98
void LineData::addAtTime(TimeFrameIndex const time, Line2D const & line, bool notify) {
6,038✔
99
    int const local_index = static_cast<int>(_data[time].size());
6,038✔
100
    EntityId entity_id = 0;
6,038✔
101
    if (_identity_registry) {
6,038✔
102
        entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index);
5,557✔
103
    }
104

105
    _data[time].emplace_back(line, entity_id);
6,038✔
106

107
    if (notify) {
6,038✔
108
        notifyObservers();
356✔
109
    }
110
}
6,038✔
111

UNCOV
112
void LineData::addPointToLine(TimeFrameIndex const time, int const line_id, Point2D<float> point, bool notify) {
×
UNCOV
113
    if (static_cast<size_t>(line_id) < _data[time].size()) {
×
114
        _data[time][static_cast<size_t>(line_id)].line.push_back(point);
×
115
    } else {
116
        std::cerr << "LineData::addPointToLine: line_id out of range" << std::endl;
×
UNCOV
117
        EntityId entity_id = 0;
×
118
        if (_identity_registry) {
×
119
            int const local_index = static_cast<int>(_data[time].size());
×
120
            entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index);
×
121
        }
122
        _data[time].emplace_back(Line2D{point}, entity_id);
×
123
    }
124

UNCOV
125
    if (notify) {
×
UNCOV
126
        notifyObservers();
×
127
    }
128
}
×
129

130
void LineData::addPointToLineInterpolate(TimeFrameIndex const time, int const line_id, Point2D<float> point, bool notify) {
×
UNCOV
131
    if (static_cast<size_t>(line_id) >= _data[time].size()) {
×
132
        std::cerr << "LineData::addPointToLineInterpolate: line_id out of range" << std::endl;
×
133
        EntityId entity_id = 0;
×
134
        if (_identity_registry) {
×
135
            int const local_index = static_cast<int>(_data[time].size());
×
136
            entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index);
×
137
        }
138
        _data[time].emplace_back(Line2D{}, entity_id);
×
139
    }
140

UNCOV
141
    Line2D & line = _data[time][static_cast<size_t>(line_id)].line;
×
UNCOV
142
    if (!line.empty()) {
×
143
        Point2D<float> const last_point = line.back();
×
144
        float const distance = std::sqrt(std::pow(point.x - last_point.x, 2.0f) + std::pow(point.y - last_point.y, 2.0f));
×
145
        int const n = static_cast<int>(distance / 2.0f);
×
146
        for (int i = 1; i <= n; ++i) {
×
147
            float const t = static_cast<float>(i) / static_cast<float>(n + 1);
×
148
            float const interp_x = last_point.x + t * (point.x - last_point.x);
×
149
            float const interp_y = last_point.y + t * (point.y - last_point.y);
×
150
            line.push_back(Point2D<float>{interp_x, interp_y});
×
151
        }
152
    }
UNCOV
153
    line.push_back(point);
×
154
    // Note: smooth_line function needs to be implemented or included
155
    // smooth_line(line);
156

UNCOV
157
    if (notify) {
×
UNCOV
158
        notifyObservers();
×
159
    }
160
}
×
161

162
void LineData::addEntryAtTime(TimeFrameIndex const time, Line2D const & line, EntityId entity_id, bool notify) {
6✔
163
    _data[time].emplace_back(line, entity_id);
6✔
164
    if (notify) {
6✔
UNCOV
165
        notifyObservers();
×
166
    }
167
}
6✔
168

169
// ========== Getters ==========
170

171
std::vector<Line2D> const & LineData::getAtTime(TimeFrameIndex const time) const {
555✔
172
    // This method needs to return a reference to a vector of Line2D objects
173
    // Since we now store LineEntry objects, we need to create a temporary vector
174
    // We'll use a member variable to store the converted data to maintain reference stability
175

176
    auto it = _data.find(time);
555✔
177
    if (it == _data.end()) {
555✔
178
        return _empty;
42✔
179
    }
180

181
    // Use a mutable member variable to store the converted lines
182
    // This is not thread-safe but maintains API compatibility
183
    _temp_lines.clear();
513✔
184
    _temp_lines.reserve(it->second.size());
513✔
185
    for (auto const & entry: it->second) {
1,282✔
186
        _temp_lines.push_back(entry.line);
769✔
187
    }
188

189
    return _temp_lines;
513✔
190
}
191

192
std::vector<Line2D> const & LineData::getAtTime(TimeFrameIndex const time,
205✔
193
                                                TimeFrame const * source_timeframe,
194
                                                TimeFrame const * line_timeframe) const {
195
    // Convert time if needed
196
    TimeFrameIndex converted_time = time;
205✔
197
    if (source_timeframe && line_timeframe && source_timeframe != line_timeframe) {
205✔
UNCOV
198
        auto time_value = source_timeframe->getTimeAtIndex(time);
×
UNCOV
199
        converted_time = line_timeframe->getIndexAtTime(static_cast<float>(time_value));
×
200
    }
201

202
    return getAtTime(converted_time);
410✔
203
}
204

205
std::vector<EntityId> const & LineData::getEntityIdsAtTime(TimeFrameIndex const time) const {
4,630✔
206
    // Similar to getAtTime, we need to create a temporary vector
207
    auto it = _data.find(time);
4,630✔
208
    if (it == _data.end()) {
4,630✔
209
        return _empty_entity_ids;
10✔
210
    }
211

212
    _temp_entity_ids.clear();
4,620✔
213
    _temp_entity_ids.reserve(it->second.size());
4,620✔
214
    for (auto const & entry: it->second) {
12,394✔
215
        _temp_entity_ids.push_back(entry.entity_id);
7,774✔
216
    }
217

218
    return _temp_entity_ids;
4,620✔
219
}
220

221
std::vector<EntityId> const & LineData::getEntityIdsAtTime(TimeFrameIndex const time,
137✔
222
                                                           TimeFrame const * source_timeframe,
223
                                                           TimeFrame const * line_timeframe) const {
224
    // Convert time if needed
225
    TimeFrameIndex converted_time = time;
137✔
226
    if (source_timeframe && line_timeframe && source_timeframe != line_timeframe) {
137✔
UNCOV
227
        auto time_value = source_timeframe->getTimeAtIndex(time);
×
UNCOV
228
        converted_time = line_timeframe->getIndexAtTime(static_cast<float>(time_value));
×
229
    }
230

231
    return getEntityIdsAtTime(converted_time);
274✔
232
}
233

234
std::vector<EntityId> LineData::getAllEntityIds() const {
57✔
235
    std::vector<EntityId> out;
57✔
236
    for (auto const & [t, entries]: _data) {
1,996✔
237
        (void) t;
238
        for (auto const & entry: entries) {
5,795✔
239
            out.push_back(entry.entity_id);
3,856✔
240
        }
241
    }
242
    return out;
57✔
UNCOV
243
}
×
244

245
// ========== Entity Lookup Methods ==========
246

247
std::optional<Line2D> LineData::getLineByEntityId(EntityId entity_id) const {
4,042✔
248
    if (!_identity_registry) {
4,042✔
249
        return std::nullopt;
7✔
250
    }
251

252
    auto descriptor = _identity_registry->get(entity_id);
4,035✔
253
    if (!descriptor || descriptor->kind != EntityKind::LineEntity || descriptor->data_key != _identity_data_key) {
4,035✔
UNCOV
254
        return std::nullopt;
×
255
    }
256

257
    TimeFrameIndex const time{descriptor->time_value};
4,035✔
258
    int const local_index = descriptor->local_index;
4,035✔
259

260
    auto time_it = _data.find(time);
4,035✔
261
    if (time_it == _data.end()) {
4,035✔
UNCOV
262
        return std::nullopt;
×
263
    }
264

265
    if (local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
4,035✔
UNCOV
266
        return std::nullopt;
×
267
    }
268

269
    return time_it->second[static_cast<size_t>(local_index)].line;
4,035✔
270
}
4,035✔
271

UNCOV
272
std::optional<std::reference_wrapper<Line2D>> LineData::getMutableLineByEntityId(EntityId entity_id) {
×
UNCOV
273
    if (!_identity_registry) {
×
UNCOV
274
        return std::nullopt;
×
275
    }
276

277
    auto descriptor = _identity_registry->get(entity_id);
×
UNCOV
278
    if (!descriptor || descriptor->kind != EntityKind::LineEntity || descriptor->data_key != _identity_data_key) {
×
UNCOV
279
        return std::nullopt;
×
280
    }
281

282
    TimeFrameIndex const time{descriptor->time_value};
×
UNCOV
283
    int const local_index = descriptor->local_index;
×
284

285
    auto time_it = _data.find(time);
×
286
    if (time_it == _data.end()) {
×
UNCOV
287
        return std::nullopt;
×
288
    }
289

290
    if (local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
×
UNCOV
291
        return std::nullopt;
×
292
    }
293

294
    return std::ref(time_it->second[static_cast<size_t>(local_index)].line);
×
UNCOV
295
}
×
296

297
std::optional<std::pair<TimeFrameIndex, int>> LineData::getTimeAndIndexByEntityId(EntityId entity_id) const {
338✔
298
    if (!_identity_registry) {
338✔
299
        return std::nullopt;
7✔
300
    }
301

302
    auto descriptor = _identity_registry->get(entity_id);
331✔
303
    if (!descriptor || descriptor->kind != EntityKind::LineEntity || descriptor->data_key != _identity_data_key) {
331✔
UNCOV
304
        return std::nullopt;
×
305
    }
306

307
    TimeFrameIndex const time{descriptor->time_value};
331✔
308
    int const local_index = descriptor->local_index;
331✔
309

310
    // Verify the time and index are valid
311
    auto time_it = _data.find(time);
331✔
312
    if (time_it == _data.end() || local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
331✔
UNCOV
313
        return std::nullopt;
×
314
    }
315

316
    return std::make_pair(time, local_index);
331✔
317
}
331✔
318

319
std::vector<std::pair<EntityId, Line2D>> LineData::getLinesByEntityIds(std::vector<EntityId> const & entity_ids) const {
5✔
320
    std::vector<std::pair<EntityId, Line2D>> results;
5✔
321
    results.reserve(entity_ids.size());
5✔
322

323
    for (EntityId const entity_id: entity_ids) {
17✔
324
        auto line = getLineByEntityId(entity_id);
12✔
325
        if (line.has_value()) {
12✔
326
            results.emplace_back(entity_id, std::move(line.value()));
7✔
327
        }
328
    }
12✔
329

330
    return results;
5✔
UNCOV
331
}
×
332

333
std::vector<std::tuple<EntityId, TimeFrameIndex, int>> LineData::getTimeInfoByEntityIds(std::vector<EntityId> const & entity_ids) const {
4✔
334
    std::vector<std::tuple<EntityId, TimeFrameIndex, int>> results;
4✔
335
    results.reserve(entity_ids.size());
4✔
336

337
    for (EntityId const entity_id: entity_ids) {
13✔
338
        auto time_info = getTimeAndIndexByEntityId(entity_id);
9✔
339
        if (time_info.has_value()) {
9✔
340
            results.emplace_back(entity_id, time_info->first, time_info->second);
4✔
341
        }
342
    }
343

344
    return results;
4✔
UNCOV
345
}
×
346

347
// ========== Image Size ==========
348

UNCOV
349
void LineData::changeImageSize(ImageSize const & image_size) {
×
UNCOV
350
    if (_image_size.width == -1 || _image_size.height == -1) {
×
351
        std::cout << "No size set for current image. "
352
                  << " Please set a valid image size before trying to scale" << std::endl;
×
353
    }
354

UNCOV
355
    if (_image_size.width == image_size.width && _image_size.height == image_size.height) {
×
356
        std::cout << "Image size is the same. No need to scale" << std::endl;
×
UNCOV
357
        return;
×
358
    }
359

360
    float const scale_x = static_cast<float>(image_size.width) / static_cast<float>(_image_size.width);
×
361
    float const scale_y = static_cast<float>(image_size.height) / static_cast<float>(_image_size.height);
×
362

UNCOV
363
    for (auto & [time, entries]: _data) {
×
364
        for (auto & entry: entries) {
×
365
            for (auto & point: entry.line) {
×
UNCOV
366
                point.x *= scale_x;
×
367
                point.y *= scale_y;
×
368
            }
369
        }
370
    }
371
    _image_size = image_size;
×
372
}
373

374
void LineData::setIdentityContext(std::string const & data_key, EntityRegistry * registry) {
387✔
375
    _identity_data_key = data_key;
387✔
376
    _identity_registry = registry;
387✔
377
}
387✔
378

379
void LineData::rebuildAllEntityIds() {
358✔
380
    if (!_identity_registry) {
358✔
UNCOV
381
        for (auto & [t, entries]: _data) {
×
UNCOV
382
            for (auto & entry: entries) {
×
UNCOV
383
                entry.entity_id = 0;
×
384
            }
385
        }
386
        return;
×
387
    }
388

389
    for (auto & [t, entries]: _data) {
1,288✔
390
        for (int i = 0; i < static_cast<int>(entries.size()); ++i) {
2,149✔
391
            entries[static_cast<size_t>(i)].entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, t, i);
1,219✔
392
        }
393
    }
394
}
395

396
// ========== Copy and Move ==========
397

398
std::size_t LineData::copyTo(LineData & target, TimeFrameInterval const & interval, bool notify) const {
8✔
399
    if (interval.start > interval.end) {
8✔
400
        std::cerr << "LineData::copyTo: interval start (" << interval.start.getValue()
1✔
401
                  << ") must be <= interval end (" << interval.end.getValue() << ")" << std::endl;
1✔
402
        return 0;
1✔
403
    }
404

405
    std::size_t total_lines_copied = 0;
7✔
406

407
    // Iterate through all times in the source data within the interval
408
    for (auto const & [time, entries]: _data) {
27✔
409
        if (time >= interval.start && time <= interval.end && !entries.empty()) {
20✔
410
            for (auto const & entry: entries) {
21✔
411
                target.addAtTime(time, entry.line, false);// Don't notify for each operation
13✔
412
                total_lines_copied++;
13✔
413
            }
414
        }
415
    }
416

417
    // Notify observer only once at the end if requested
418
    if (notify && total_lines_copied > 0) {
7✔
419
        target.notifyObservers();
5✔
420
    }
421

422
    return total_lines_copied;
7✔
423
}
424

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

428
    // Copy lines for each specified time
429
    for (TimeFrameIndex const time: times) {
3✔
430
        auto it = _data.find(time);
2✔
431
        if (it != _data.end() && !it->second.empty()) {
2✔
432
            for (auto const & entry: it->second) {
5✔
433
                target.addAtTime(time, entry.line, false);// Don't notify for each operation
3✔
434
                total_lines_copied++;
3✔
435
            }
436
        }
437
    }
438

439
    // Notify observer only once at the end if requested
440
    if (notify && total_lines_copied > 0) {
1✔
441
        target.notifyObservers();
1✔
442
    }
443

444
    return total_lines_copied;
1✔
445
}
446

447
std::size_t LineData::moveTo(LineData & target, TimeFrameInterval const & interval, bool notify) {
4✔
448
    if (interval.start > interval.end) {
4✔
UNCOV
449
        std::cerr << "LineData::moveTo: interval start (" << interval.start.getValue()
×
UNCOV
450
                  << ") must be <= interval end (" << interval.end.getValue() << ")" << std::endl;
×
UNCOV
451
        return 0;
×
452
    }
453

454
    std::size_t total_lines_moved = 0;
4✔
455

456
    auto start_it = _data.lower_bound(interval.start);
4✔
457
    auto end_it = _data.upper_bound(interval.end);
4✔
458

459
    for (auto it = start_it; it != end_it;) {
9✔
460
        auto node = _data.extract(it++);
5✔
461
        total_lines_moved += node.mapped().size();
5✔
462
        auto [target_it, inserted, node_handle] = target._data.insert(std::move(node));
5✔
463
        if (!inserted) {
5✔
464
            // If the key already exists in the target, merge the vectors
UNCOV
465
            target_it->second.insert(target_it->second.end(),
×
UNCOV
466
                                     std::make_move_iterator(node_handle.mapped().begin()),
×
UNCOV
467
                                     std::make_move_iterator(node_handle.mapped().end()));
×
468
        }
469
    }
5✔
470

471
    if (notify && total_lines_moved > 0) {
4✔
472
        target.notifyObservers();
3✔
473
        notifyObservers();
3✔
474
    }
475

476
    return total_lines_moved;
4✔
477
}
478

479
std::size_t LineData::moveTo(LineData & target, std::vector<TimeFrameIndex> const & times, bool notify) {
2✔
480
    std::size_t total_lines_moved = 0;
2✔
481

482
    for (TimeFrameIndex const time: times) {
6✔
483
        if (auto it = _data.find(time); it != _data.end()) {
4✔
484
            auto node = _data.extract(it);
2✔
485
            total_lines_moved += node.mapped().size();
2✔
486
            auto [target_it, inserted, node_handle] = target._data.insert(std::move(node));
2✔
487
            if (!inserted) {
2✔
488
                // If the key already exists in the target, merge the vectors
UNCOV
489
                target_it->second.insert(target_it->second.end(),
×
UNCOV
490
                                         std::make_move_iterator(node_handle.mapped().begin()),
×
UNCOV
491
                                         std::make_move_iterator(node_handle.mapped().end()));
×
492
            }
493
        }
2✔
494
    }
495

496
    if (notify && total_lines_moved > 0) {
2✔
497
        target.notifyObservers();
1✔
498
        notifyObservers();
1✔
499
    }
500

501
    return total_lines_moved;
2✔
502
}
503

504
std::size_t LineData::copyByEntityIds(LineData & target, std::unordered_set<EntityId> const & entity_ids, bool notify) {
5✔
505
    return copy_by_entity_ids(_data, target, entity_ids, notify,
5✔
506
                              [](LineEntry const & entry) -> Line2D const & { return entry.line; });
11✔
507
}
508

509
std::size_t LineData::moveByEntityIds(LineData & target, std::unordered_set<EntityId> const & entity_ids, bool notify) {
4✔
510
    auto result = move_by_entity_ids(_data, target, entity_ids, notify,
4✔
511
                                     [](LineEntry const & entry) -> Line2D const & { return entry.line; });
6✔
512
    
513
    if (notify && result > 0) {
4✔
514
        notifyObservers();
3✔
515
    }
516
    
517
    return result;
4✔
518
}
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