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

paulmthompson / WhiskerToolbox / 17920603410

22 Sep 2025 03:39PM UTC coverage: 71.97% (-0.05%) from 72.02%
17920603410

push

github

paulmthompson
all tests pass

277 of 288 new or added lines in 8 files covered. (96.18%)

520 existing lines in 35 files now uncovered.

40275 of 55961 relevant lines covered (71.97%)

1225.8 hits per line

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

38.53
/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
    : _data(data)
41✔
15
{
16

17
}
41✔
18

19
// ========== Setters ==========
20

21
bool LineData::clearAtTime(TimeFrameIndex const time, bool notify) {
×
22

23
    if (clear_at_time(time, _data)) {
×
24
        _entity_ids_by_time.erase(time);
×
25
        if (notify) {
×
26
            notifyObservers();
×
27
        }
28
        return true;
×
29
    }
30
    return false;   
×
31
}
32

33
bool LineData::clearAtTime(TimeFrameIndex const time, int const line_id, bool notify) {
×
34

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

53
void LineData::addAtTime(TimeFrameIndex const time, std::vector<float> const & x, std::vector<float> const & y, bool notify) {
239✔
54

55
    auto new_line = create_line(x, y);
239✔
56
    add_at_time(time, new_line, _data);
239✔
57

58
    int const local_index = static_cast<int>(_data[time].size()) - 1;
239✔
59
    if (_identity_registry) {
239✔
UNCOV
60
        _entity_ids_by_time[time].push_back(
×
UNCOV
61
            _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index)
×
62
        );
63
    } else {
64
        _entity_ids_by_time[time].push_back(0);
239✔
65
    }
66

67
    if (notify) {
239✔
68
        notifyObservers();
101✔
69
    }
70
}
478✔
71

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

76
void LineData::addAtTime(TimeFrameIndex const time, Line2D const & line, bool notify) {
437✔
77
    add_at_time(time, line, _data);
437✔
78

79
    int const local_index = static_cast<int>(_data[time].size()) - 1;
437✔
80
    if (_identity_registry) {
437✔
81
        _entity_ids_by_time[time].push_back(
×
82
            _identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, time, local_index)
×
83
        );
84
    } else {
85
        _entity_ids_by_time[time].push_back(0);
437✔
86
    }
87

88
    if (notify) {
437✔
89
        notifyObservers();
334✔
90
    }
91
}
437✔
92

93
void LineData::addPointToLine(TimeFrameIndex const time, int const line_id, Point2D<float> point, bool notify) {
×
94

95
    if (static_cast<size_t>(line_id) < _data[time].size()) {
×
96
        _data[time][static_cast<size_t>(line_id)].push_back(point);
×
97
    } else {
98
        std::cerr << "LineData::addPointToLine: line_id out of range" << std::endl;
×
99
        _data[time].emplace_back();
×
100
        _data[time].back().push_back(point);
×
101
    }
102

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

108
void LineData::addPointToLineInterpolate(TimeFrameIndex const time, int const line_id, Point2D<float> point, bool notify) {
×
109

110
    if (static_cast<size_t>(line_id) >= _data[time].size()) {
×
111
        std::cerr << "LineData::addPointToLineInterpolate: line_id out of range" << std::endl;
×
112
        _data[time].emplace_back();
×
113
    }
114

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

130
    if (notify) {
×
131
        notifyObservers();
×
132
    }
133
}
×
134

135
// ========== Getters ==========
136

137
std::vector<Line2D> const & LineData::getAtTime(TimeFrameIndex const time) const {
182✔
138
    return get_at_time(time, _data, _empty);
182✔
139
}
140

141
std::vector<Line2D> const & LineData::getAtTime(TimeFrameIndex const time, 
115✔
142
                                                TimeFrame const * source_timeframe,
143
                                                TimeFrame const * line_timeframe) const {
144

145
    return get_at_time(time, _data, _empty, source_timeframe, line_timeframe);
115✔
146
}
147

148
std::vector<EntityId> const & LineData::getEntityIdsAtTime(TimeFrameIndex const time) const {
51✔
149
    auto it = _entity_ids_by_time.find(time);
51✔
150
    if (it == _entity_ids_by_time.end()) {
51✔
151
        static const std::vector<EntityId> kEmpty;
×
152
        return kEmpty;
×
153
    }
154
    return it->second;
51✔
155
}
156

157
std::vector<EntityId> const & LineData::getEntityIdsAtTime(TimeFrameIndex const time,
55✔
158
                                                           TimeFrame const * source_timeframe,
159
                                                           TimeFrame const * line_timeframe) const {
160
    static const std::vector<EntityId> empty = std::vector<EntityId>();
55✔
161
    return get_at_time(time, _entity_ids_by_time, empty, source_timeframe, line_timeframe);
55✔
162
}
163

164
std::vector<EntityId> LineData::getAllEntityIds() const {
26✔
165
    std::vector<EntityId> out;
26✔
166
    for (auto const & [t, ids] : _entity_ids_by_time) {
104✔
167
        (void)t;
168
        out.insert(out.end(), ids.begin(), ids.end());
78✔
169
    }
170
    return out;
26✔
UNCOV
171
}
×
172

173
// ========== Entity Lookup Methods ==========
174

175
std::optional<Line2D> LineData::getLineByEntityId(EntityId entity_id) const {
12✔
176
    if (!_identity_registry) {
12✔
UNCOV
177
        return std::nullopt;
×
178
    }
179
    
180
    auto descriptor = _identity_registry->get(entity_id);
12✔
181
    if (!descriptor || descriptor->kind != EntityKind::LineEntity || descriptor->data_key != _identity_data_key) {
12✔
UNCOV
182
        return std::nullopt;
×
183
    }
184
    
185
    TimeFrameIndex time{descriptor->time_value};
12✔
186
    int local_index = descriptor->local_index;
12✔
187
    
188
    auto time_it = _data.find(time);
12✔
189
    if (time_it == _data.end()) {
12✔
UNCOV
190
        return std::nullopt;
×
191
    }
192
    
193
    if (local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
12✔
UNCOV
194
        return std::nullopt;
×
195
    }
196
    
197
    return time_it->second[static_cast<size_t>(local_index)];
12✔
198
}
12✔
199

200
std::optional<std::pair<TimeFrameIndex, int>> LineData::getTimeAndIndexByEntityId(EntityId entity_id) const {
9✔
201
    if (!_identity_registry) {
9✔
UNCOV
202
        return std::nullopt;
×
203
    }
204
    
205
    auto descriptor = _identity_registry->get(entity_id);
9✔
206
    if (!descriptor || descriptor->kind != EntityKind::LineEntity || descriptor->data_key != _identity_data_key) {
9✔
UNCOV
207
        return std::nullopt;
×
208
    }
209
    
210
    TimeFrameIndex time{descriptor->time_value};
9✔
211
    int local_index = descriptor->local_index;
9✔
212
    
213
    // Verify the time and index are valid
214
    auto time_it = _data.find(time);
9✔
215
    if (time_it == _data.end() || local_index < 0 || static_cast<size_t>(local_index) >= time_it->second.size()) {
9✔
UNCOV
216
        return std::nullopt;
×
217
    }
218
    
219
    return std::make_pair(time, local_index);
9✔
220
}
9✔
221

222
std::vector<std::pair<EntityId, Line2D>> LineData::getLinesByEntityIds(std::vector<EntityId> const & entity_ids) const {
3✔
223
    std::vector<std::pair<EntityId, Line2D>> results;
3✔
224
    results.reserve(entity_ids.size());
3✔
225
    
226
    for (EntityId entity_id : entity_ids) {
10✔
227
        auto line = getLineByEntityId(entity_id);
7✔
228
        if (line.has_value()) {
7✔
229
            results.emplace_back(entity_id, std::move(line.value()));
7✔
230
        }
231
    }
7✔
232
    
233
    return results;
3✔
UNCOV
234
}
×
235

236
std::vector<std::tuple<EntityId, TimeFrameIndex, int>> LineData::getTimeInfoByEntityIds(std::vector<EntityId> const & entity_ids) const {
2✔
237
    std::vector<std::tuple<EntityId, TimeFrameIndex, int>> results;
2✔
238
    results.reserve(entity_ids.size());
2✔
239
    
240
    for (EntityId entity_id : entity_ids) {
6✔
241
        auto time_info = getTimeAndIndexByEntityId(entity_id);
4✔
242
        if (time_info.has_value()) {
4✔
243
            results.emplace_back(entity_id, time_info->first, time_info->second);
4✔
244
        }
245
    }
246
    
247
    return results;
2✔
UNCOV
248
}
×
249

250
// ========== Image Size ==========
251

252
void LineData::changeImageSize(ImageSize const & image_size)
×
253
{
254
    if (_image_size.width == -1 || _image_size.height == -1) {
×
255
        std::cout << "No size set for current image. "
UNCOV
256
                  << " Please set a valid image size before trying to scale" << std::endl;
×
257
    }
258

UNCOV
259
    if (_image_size.width == image_size.width && _image_size.height == image_size.height) {
×
260
        std::cout << "Image size is the same. No need to scale" << std::endl;
×
261
        return;
×
262
    }
263

264
    float const scale_x = static_cast<float>(image_size.width) / static_cast<float>(_image_size.width);
×
UNCOV
265
    float const scale_y = static_cast<float>(image_size.height) / static_cast<float>(_image_size.height);
×
266

UNCOV
267
    for (auto & [time, lines] : _data) {
×
268
        for (auto & line : lines) {
×
UNCOV
269
            for (auto & point : line) {
×
UNCOV
270
                point.x *= scale_x;
×
UNCOV
271
                point.y *= scale_y;
×
272
            }
273
        }
274
    }
UNCOV
275
    _image_size = image_size;
×
276

277
}
278

279
void LineData::setIdentityContext(std::string const & data_key, EntityRegistry * registry) {
37✔
280
    _identity_data_key = data_key;
37✔
281
    _identity_registry = registry;
37✔
282
}
37✔
283

284
void LineData::rebuildAllEntityIds() {
37✔
285
    if (!_identity_registry) {
37✔
UNCOV
286
        for (auto & [t, lines] : _data) {
×
UNCOV
287
            _entity_ids_by_time[t].assign(lines.size(), 0);
×
288
        }
UNCOV
289
        return;
×
290
    }
291
    for (auto & [t, lines] : _data) {
171✔
292
        auto & ids = _entity_ids_by_time[t];
134✔
293
        ids.clear();
134✔
294
        ids.reserve(lines.size());
134✔
295
        for (int i = 0; i < static_cast<int>(lines.size()); ++i) {
330✔
296
            ids.push_back(_identity_registry->ensureId(_identity_data_key, EntityKind::LineEntity, t, i));
196✔
297
        }
298
    }
299
}
300

301
// ========== Copy and Move ==========
302

303
std::size_t LineData::copyTo(LineData& target, TimeFrameInterval const & interval, bool notify) const {
×
UNCOV
304
    if (interval.start > interval.end) {
×
UNCOV
305
        std::cerr << "LineData::copyTo: interval start (" << interval.start.getValue() 
×
306
                  << ") must be <= interval end (" << interval.end.getValue() << ")" << std::endl;
×
307
        return 0;
×
308
    }
309

310
    std::size_t total_lines_copied = 0;
×
311

312
    // Iterate through all times in the source data within the interval
UNCOV
313
    for (auto const & [time, lines] : _data) {
×
UNCOV
314
        if (time >= interval.start && time <= interval.end && !lines.empty()) {
×
UNCOV
315
            for (auto const& line : lines) {
×
316
                target.addAtTime(time, line, false); // Don't notify for each operation
×
317
                total_lines_copied++;
×
318
            }
319
        }
320
    }
321

322
    // Notify observer only once at the end if requested
323
    if (notify && total_lines_copied > 0) {
×
324
        target.notifyObservers();
×
325
    }
326

327
    return total_lines_copied;
×
328
}
329

330
std::size_t LineData::copyTo(LineData& target, std::vector<TimeFrameIndex> const& times, bool notify) const {
×
331
    std::size_t total_lines_copied = 0;
×
332

333
    // Copy lines for each specified time
UNCOV
334
    for (TimeFrameIndex time : times) {
×
UNCOV
335
        auto it = _data.find(time);
×
UNCOV
336
        if (it != _data.end() && !it->second.empty()) {
×
UNCOV
337
            for (auto const& line : it->second) {
×
338
                target.addAtTime(time, line, false); // Don't notify for each operation
×
339
                total_lines_copied++;
×
340
            }
341
        }
342
    }
343

344
    // Notify observer only once at the end if requested
345
    if (notify && total_lines_copied > 0) {
×
346
        target.notifyObservers();
×
347
    }
348

349
    return total_lines_copied;
×
350
}
351

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

359
    std::size_t total_lines_moved = 0;
×
360
    std::vector<TimeFrameIndex> times_to_clear;
×
361

362
    // First, copy all lines in the interval to target
UNCOV
363
    for (auto const & [time, lines] : _data) {
×
UNCOV
364
        if (time >= interval.start && time <= interval.end && !lines.empty()) {
×
UNCOV
365
            for (auto const& line : lines) {
×
UNCOV
366
                target.addAtTime(time, line, false); // Don't notify for each operation
×
367
                total_lines_moved++;
×
368
            }
UNCOV
369
            times_to_clear.push_back(time);
×
370
        }
371
    }
372

373
    // Then, clear all the times from source
374
    for (TimeFrameIndex time : times_to_clear) {
×
UNCOV
375
        (void)clearAtTime(time, false); // Don't notify for each operation
×
376
    }
377

378
    // Notify observers only once at the end if requested
UNCOV
379
    if (notify && total_lines_moved > 0) {
×
380
        target.notifyObservers();
×
381
        notifyObservers();
×
382
    }
383

UNCOV
384
    return total_lines_moved;
×
385
}
×
386

387
std::size_t LineData::moveTo(LineData& target, std::vector<TimeFrameIndex> const & times, bool notify) {
×
388
    std::size_t total_lines_moved = 0;
×
389
    std::vector<TimeFrameIndex> times_to_clear;
×
390

391
    // First, copy lines for each specified time to target
392
    for (TimeFrameIndex time : times) {
×
UNCOV
393
        auto it = _data.find(time);
×
UNCOV
394
        if (it != _data.end() && !it->second.empty()) {
×
UNCOV
395
            for (auto const& line : it->second) {
×
UNCOV
396
                target.addAtTime(time, line, false); // Don't notify for each operation
×
397
                total_lines_moved++;
×
398
            }
UNCOV
399
            times_to_clear.push_back(time);
×
400
        }
401
    }
402

403
    // Then, clear all the times from source
404
    for (TimeFrameIndex time : times_to_clear) {
×
UNCOV
405
        (void)clearAtTime(time, false); // Don't notify for each operation
×
406
    }
407

408
    // Notify observers only once at the end if requested
UNCOV
409
    if (notify && total_lines_moved > 0) {
×
UNCOV
410
        target.notifyObservers();
×
UNCOV
411
        notifyObservers();
×
412
    }
413

UNCOV
414
    return total_lines_moved;
×
UNCOV
415
}
×
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