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

paulmthompson / WhiskerToolbox / 18117112849

30 Sep 2025 02:44AM UTC coverage: 70.161% (+0.03%) from 70.132%
18117112849

push

github

paulmthompson
hungarian algorithm is actually used

60 of 77 new or added lines in 2 files covered. (77.92%)

352 existing lines in 12 files now uncovered.

45125 of 64316 relevant lines covered (70.16%)

1116.85 hits per line

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

42.75
/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) {
×
26
    auto it = _data.find(time);
×
UNCOV
27
    if (it != _data.end()) {
×
28
        _data.erase(it);
×
UNCOV
29
        if (notify) {
×
30
            notifyObservers();
×
31
        }
UNCOV
32
        return true;
×
33
    }
UNCOV
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()) {
×
UNCOV
40
        it->second.erase(it->second.begin() + static_cast<long int>(line_id));
×
41
        if (it->second.empty()) {
×
42
            _data.erase(it);
×
43
        }
UNCOV
44
        if (notify) {
×
45
            notifyObservers();
×
46
        }
UNCOV
47
        return true;
×
48
    }
UNCOV
49
    return false;
×
50
}
51

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

66
    if (notify) {
481✔
67
        notifyObservers();
101✔
68
    }
69
}
962✔
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,454✔
76
    int const local_index = static_cast<int>(_data[time].size());
2,454✔
77
    EntityId entity_id = 0;
2,454✔
78
    if (_identity_registry) {
2,454✔
79
        entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index);
2,000✔
80
    }
81
    
82
    _data[time].emplace_back(line, entity_id);
2,454✔
83

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

UNCOV
89
void LineData::addPointToLine(TimeFrameIndex const time, int const line_id, Point2D<float> point, bool notify) {
×
UNCOV
90
    if (static_cast<size_t>(line_id) < _data[time].size()) {
×
UNCOV
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;
×
UNCOV
94
        EntityId entity_id = 0;
×
95
        if (_identity_registry) {
×
96
            int const local_index = static_cast<int>(_data[time].size());
×
UNCOV
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

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

UNCOV
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()) {
×
UNCOV
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());
×
UNCOV
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);
×
UNCOV
125
            float const interp_x = last_point.x + t * (point.x - last_point.x);
×
UNCOV
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

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

139
// ========== Getters ==========
140

141
std::vector<Line2D> const & LineData::getAtTime(TimeFrameIndex const time) const {
387✔
142
    // This method needs to return a reference to a vector of Line2D objects
143
    // Since we now store LineEntry objects, we need to create a temporary vector
144
    // We'll use a member variable to store the converted data to maintain reference stability
145
    
146
    auto it = _data.find(time);
387✔
147
    if (it == _data.end()) {
387✔
148
        return _empty;
25✔
149
    }
150
    
151
    // Use a mutable member variable to store the converted lines
152
    // This is not thread-safe but maintains API compatibility
153
    _temp_lines.clear();
362✔
154
    _temp_lines.reserve(it->second.size());
362✔
155
    for (auto const & entry : it->second) {
860✔
156
        _temp_lines.push_back(entry.line);
498✔
157
    }
158
    
159
    return _temp_lines;
362✔
160
}
161

162
std::vector<Line2D> const & LineData::getAtTime(TimeFrameIndex const time, 
205✔
163
                                                TimeFrame const * source_timeframe,
164
                                                TimeFrame const * line_timeframe) const {
165
    // Convert time if needed
166
    TimeFrameIndex converted_time = time;
205✔
167
    if (source_timeframe && line_timeframe && source_timeframe != line_timeframe) {
205✔
UNCOV
168
        auto time_value = source_timeframe->getTimeAtIndex(time);
×
UNCOV
169
        converted_time = line_timeframe->getIndexAtTime(static_cast<float>(time_value));
×
170
    }
171
    
172
    return getAtTime(converted_time);
410✔
173
}
174

175
std::vector<EntityId> const & LineData::getEntityIdsAtTime(TimeFrameIndex const time) const {
2,291✔
176
    // Similar to getAtTime, we need to create a temporary vector
177
    auto it = _data.find(time);
2,291✔
178
    if (it == _data.end()) {
2,291✔
179
        return _empty_entity_ids;
9✔
180
    }
181
    
182
    _temp_entity_ids.clear();
2,282✔
183
    _temp_entity_ids.reserve(it->second.size());
2,282✔
184
    for (auto const & entry : it->second) {
6,789✔
185
        _temp_entity_ids.push_back(entry.entity_id);
4,507✔
186
    }
187
    
188
    return _temp_entity_ids;
2,282✔
189
}
190

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

204
std::vector<EntityId> LineData::getAllEntityIds() const {
41✔
205
    std::vector<EntityId> out;
41✔
206
    for (auto const & [t, entries] : _data) {
1,520✔
207
        (void)t;
208
        for (auto const & entry : entries) {
4,412✔
209
            out.push_back(entry.entity_id);
2,933✔
210
        }
211
    }
212
    return out;
41✔
UNCOV
213
}
×
214

215
// ========== Entity Lookup Methods ==========
216

217
std::optional<Line2D> LineData::getLineByEntityId(EntityId entity_id) const {
5,076✔
218
    if (!_identity_registry) {
5,076✔
UNCOV
219
        return std::nullopt;
×
220
    }
221
    
222
    auto descriptor = _identity_registry->get(entity_id);
5,076✔
223
    if (!descriptor || descriptor->kind != EntityKind::LineEntity || descriptor->data_key != _identity_data_key) {
5,076✔
UNCOV
224
        return std::nullopt;
×
225
    }
226
    
227
    TimeFrameIndex time{descriptor->time_value};
5,076✔
228
    int local_index = descriptor->local_index;
5,076✔
229
    
230
    auto time_it = _data.find(time);
5,076✔
231
    if (time_it == _data.end()) {
5,076✔
UNCOV
232
        return std::nullopt;
×
233
    }
234
    
235
    if (local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
5,076✔
UNCOV
236
        return std::nullopt;
×
237
    }
238
    
239
    return time_it->second[static_cast<size_t>(local_index)].line;
5,076✔
240
}
5,076✔
241

242
std::optional<std::pair<TimeFrameIndex, int>> LineData::getTimeAndIndexByEntityId(EntityId entity_id) const {
89✔
243
    if (!_identity_registry) {
89✔
244
        return std::nullopt;
20✔
245
    }
246
    
247
    auto descriptor = _identity_registry->get(entity_id);
69✔
248
    if (!descriptor || descriptor->kind != EntityKind::LineEntity || descriptor->data_key != _identity_data_key) {
69✔
UNCOV
249
        return std::nullopt;
×
250
    }
251
    
252
    TimeFrameIndex time{descriptor->time_value};
69✔
253
    int local_index = descriptor->local_index;
69✔
254
    
255
    // Verify the time and index are valid
256
    auto time_it = _data.find(time);
69✔
257
    if (time_it == _data.end() || local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
69✔
UNCOV
258
        return std::nullopt;
×
259
    }
260
    
261
    return std::make_pair(time, local_index);
69✔
262
}
69✔
263

264
std::vector<std::pair<EntityId, Line2D>> LineData::getLinesByEntityIds(std::vector<EntityId> const & entity_ids) const {
3✔
265
    std::vector<std::pair<EntityId, Line2D>> results;
3✔
266
    results.reserve(entity_ids.size());
3✔
267
    
268
    for (EntityId entity_id : entity_ids) {
10✔
269
        auto line = getLineByEntityId(entity_id);
7✔
270
        if (line.has_value()) {
7✔
271
            results.emplace_back(entity_id, std::move(line.value()));
7✔
272
        }
273
    }
7✔
274
    
275
    return results;
3✔
UNCOV
276
}
×
277

278
std::vector<std::tuple<EntityId, TimeFrameIndex, int>> LineData::getTimeInfoByEntityIds(std::vector<EntityId> const & entity_ids) const {
2✔
279
    std::vector<std::tuple<EntityId, TimeFrameIndex, int>> results;
2✔
280
    results.reserve(entity_ids.size());
2✔
281
    
282
    for (EntityId entity_id : entity_ids) {
6✔
283
        auto time_info = getTimeAndIndexByEntityId(entity_id);
4✔
284
        if (time_info.has_value()) {
4✔
285
            results.emplace_back(entity_id, time_info->first, time_info->second);
4✔
286
        }
287
    }
288
    
289
    return results;
2✔
UNCOV
290
}
×
291

292
// ========== Image Size ==========
293

UNCOV
294
void LineData::changeImageSize(ImageSize const & image_size)
×
295
{
UNCOV
296
    if (_image_size.width == -1 || _image_size.height == -1) {
×
297
        std::cout << "No size set for current image. "
UNCOV
298
                  << " Please set a valid image size before trying to scale" << std::endl;
×
299
    }
300

UNCOV
301
    if (_image_size.width == image_size.width && _image_size.height == image_size.height) {
×
UNCOV
302
        std::cout << "Image size is the same. No need to scale" << std::endl;
×
303
        return;
×
304
    }
305

306
    float const scale_x = static_cast<float>(image_size.width) / static_cast<float>(_image_size.width);
×
307
    float const scale_y = static_cast<float>(image_size.height) / static_cast<float>(_image_size.height);
×
308

UNCOV
309
    for (auto & [time, entries] : _data) {
×
310
        for (auto & entry : entries) {
×
UNCOV
311
            for (auto & point : entry.line) {
×
UNCOV
312
                point.x *= scale_x;
×
313
                point.y *= scale_y;
×
314
            }
315
        }
316
    }
317
    _image_size = image_size;
×
318
}
319

320
void LineData::setIdentityContext(std::string const & data_key, EntityRegistry * registry) {
315✔
321
    _identity_data_key = data_key;
315✔
322
    _identity_registry = registry;
315✔
323
}
315✔
324

325
void LineData::rebuildAllEntityIds() {
315✔
326
    if (!_identity_registry) {
315✔
327
        for (auto & [t, entries] : _data) {
×
UNCOV
328
            for (auto & entry : entries) {
×
UNCOV
329
                entry.entity_id = 0;
×
330
            }
331
        }
UNCOV
332
        return;
×
333
    }
334
    
335
    for (auto & [t, entries] : _data) {
1,245✔
336
        for (int i = 0; i < static_cast<int>(entries.size()); ++i) {
2,149✔
337
            entries[static_cast<size_t>(i)].entity_id = _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, t, i);
1,219✔
338
        }
339
    }
340
}
341

342
// ========== Copy and Move ==========
343

UNCOV
344
std::size_t LineData::copyTo(LineData& target, TimeFrameInterval const & interval, bool notify) const {
×
345
    if (interval.start > interval.end) {
×
346
        std::cerr << "LineData::copyTo: interval start (" << interval.start.getValue() 
×
UNCOV
347
                  << ") must be <= interval end (" << interval.end.getValue() << ")" << std::endl;
×
UNCOV
348
        return 0;
×
349
    }
350

UNCOV
351
    std::size_t total_lines_copied = 0;
×
352

353
    // Iterate through all times in the source data within the interval
354
    for (auto const & [time, entries] : _data) {
×
355
        if (time >= interval.start && time <= interval.end && !entries.empty()) {
×
356
            for (auto const& entry : entries) {
×
UNCOV
357
                target.addAtTime(time, entry.line, false); // Don't notify for each operation
×
UNCOV
358
                total_lines_copied++;
×
359
            }
360
        }
361
    }
362

363
    // Notify observer only once at the end if requested
364
    if (notify && total_lines_copied > 0) {
×
365
        target.notifyObservers();
×
366
    }
367

UNCOV
368
    return total_lines_copied;
×
369
}
370

UNCOV
371
std::size_t LineData::copyTo(LineData& target, std::vector<TimeFrameIndex> const& times, bool notify) const {
×
UNCOV
372
    std::size_t total_lines_copied = 0;
×
373

374
    // Copy lines for each specified time
375
    for (TimeFrameIndex time : times) {
×
UNCOV
376
        auto it = _data.find(time);
×
UNCOV
377
        if (it != _data.end() && !it->second.empty()) {
×
UNCOV
378
            for (auto const& entry : it->second) {
×
379
                target.addAtTime(time, entry.line, false); // Don't notify for each operation
×
380
                total_lines_copied++;
×
381
            }
382
        }
383
    }
384

385
    // Notify observer only once at the end if requested
UNCOV
386
    if (notify && total_lines_copied > 0) {
×
387
        target.notifyObservers();
×
388
    }
389

UNCOV
390
    return total_lines_copied;
×
391
}
392

393
std::size_t LineData::moveTo(LineData& target, TimeFrameInterval const & interval, bool notify) {
×
394
    if (interval.start > interval.end) {
×
395
        std::cerr << "LineData::moveTo: interval start (" << interval.start.getValue() 
×
396
                  << ") must be <= interval end (" << interval.end.getValue() << ")" << std::endl;
×
397
        return 0;
×
398
    }
399

UNCOV
400
    std::size_t total_lines_moved = 0;
×
UNCOV
401
    std::vector<TimeFrameIndex> times_to_clear;
×
402

403
    // First, copy all lines in the interval to target
404
    for (auto const & [time, entries] : _data) {
×
405
        if (time >= interval.start && time <= interval.end && !entries.empty()) {
×
UNCOV
406
            for (auto const& entry : entries) {
×
UNCOV
407
                target.addAtTime(time, entry.line, false); // Don't notify for each operation
×
UNCOV
408
                total_lines_moved++;
×
409
            }
410
            times_to_clear.push_back(time);
×
411
        }
412
    }
413

414
    // Then, clear all the times from source
415
    for (TimeFrameIndex time : times_to_clear) {
×
UNCOV
416
        (void)clearAtTime(time, false); // Don't notify for each operation
×
417
    }
418

419
    // Notify observers only once at the end if requested
UNCOV
420
    if (notify && total_lines_moved > 0) {
×
UNCOV
421
        target.notifyObservers();
×
UNCOV
422
        notifyObservers();
×
423
    }
424

UNCOV
425
    return total_lines_moved;
×
UNCOV
426
}
×
427

UNCOV
428
std::size_t LineData::moveTo(LineData& target, std::vector<TimeFrameIndex> const & times, bool notify) {
×
UNCOV
429
    std::size_t total_lines_moved = 0;
×
UNCOV
430
    std::vector<TimeFrameIndex> times_to_clear;
×
431

432
    // First, copy lines for each specified time to target
UNCOV
433
    for (TimeFrameIndex time : times) {
×
UNCOV
434
        auto it = _data.find(time);
×
UNCOV
435
        if (it != _data.end() && !it->second.empty()) {
×
UNCOV
436
            for (auto const& entry : it->second) {
×
UNCOV
437
                target.addAtTime(time, entry.line, false); // Don't notify for each operation
×
UNCOV
438
                total_lines_moved++;
×
439
            }
UNCOV
440
            times_to_clear.push_back(time);
×
441
        }
442
    }
443

444
    // Then, clear all the times from source
UNCOV
445
    for (TimeFrameIndex time : times_to_clear) {
×
UNCOV
446
        (void)clearAtTime(time, false); // Don't notify for each operation
×
447
    }
448

449
    // Notify observers only once at the end if requested
UNCOV
450
    if (notify && total_lines_moved > 0) {
×
UNCOV
451
        target.notifyObservers();
×
UNCOV
452
        notifyObservers();
×
453
    }
454

UNCOV
455
    return total_lines_moved;
×
UNCOV
456
}
×
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