• 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

52.91
/src/DataManager/DigitalTimeSeries/Digital_Interval_Series.cpp
1
#include "Digital_Interval_Series.hpp"
2
#include "Entity/EntityRegistry.hpp"
3

4
#include <algorithm>
5
#include <ranges>
6
#include <utility>
7
#include <vector>
8

9
// ========== Constructors ==========
10

11
DigitalIntervalSeries::DigitalIntervalSeries(std::vector<Interval> digital_vector) {
103✔
12
    _data = std::move(digital_vector);
103✔
13
    _sortData();
103✔
14
}
103✔
15

16
DigitalIntervalSeries::DigitalIntervalSeries(std::vector<std::pair<float, float>> const & digital_vector) {
1✔
17
    std::vector<Interval> intervals;
1✔
18
    intervals.reserve(digital_vector.size());
1✔
19
    for (auto & interval: digital_vector) {
6✔
20
        intervals.emplace_back(Interval{static_cast<int64_t>(interval.first), static_cast<int64_t>(interval.second)});
5✔
21
    }
22
    _data = std::move(intervals);
1✔
23
    _sortData();
1✔
24
}
2✔
25

26
// ========== Getters ==========
27

28
std::vector<Interval> const & DigitalIntervalSeries::getDigitalIntervalSeries() const {
147✔
29
    return _data;
147✔
30
}
31

UNCOV
32
bool DigitalIntervalSeries::isEventAtTime(TimeFrameIndex const time) const {
×
33

34
    auto Contained = [time](auto const & event) {
×
35
        return is_contained(event, time.getValue());
×
UNCOV
36
    };
×
37

UNCOV
38
    if (std::ranges::any_of(_data, Contained)) return true;
×
39

UNCOV
40
    return false;
×
41
}
42

43
void DigitalIntervalSeries::addEvent(Interval new_interval) {
546✔
44
    _addEvent(new_interval);
546✔
45

46
    notifyObservers();
546✔
47
}
546✔
48

49
void DigitalIntervalSeries::_addEvent(Interval new_interval) {
546✔
50
    auto it = _data.begin();
546✔
51
    while (it != _data.end()) {
1,301✔
52
        if (is_overlapping(*it, new_interval) || is_contiguous(*it, new_interval)) {
755✔
53
            new_interval.start = std::min(new_interval.start, it->start);
16✔
54
            new_interval.end = std::max(new_interval.end, it->end);
16✔
55
            it = _data.erase(it);
16✔
56
        } else if (is_contained(new_interval, *it)) {
739✔
57
            // The new interval is completely contained within an existing interval, so we do nothing.
UNCOV
58
            return;
×
59
        } else {
60
            ++it;
739✔
61
        }
62
    }
63
    _data.push_back(new_interval);
546✔
64
    _sortData();
546✔
65
}
66

67
void DigitalIntervalSeries::setEventAtTime(TimeFrameIndex time, bool const event) {
×
68
    _setEventAtTime(time, event);
×
69
    notifyObservers();
×
UNCOV
70
}
×
71

72
bool DigitalIntervalSeries::removeInterval(Interval const & interval) {
×
73
    auto it = std::ranges::find(_data, interval);
×
74
    if (it != _data.end()) {
×
75
        _data.erase(it);
×
76
        notifyObservers();
×
UNCOV
77
        return true;
×
78
    }
UNCOV
79
    return false;
×
80
}
81

82
size_t DigitalIntervalSeries::removeIntervals(std::vector<Interval> const & intervals) {
×
UNCOV
83
    size_t removed_count = 0;
×
84

85
    for (auto const & interval: intervals) {
×
86
        auto it = std::ranges::find(_data, interval);
×
87
        if (it != _data.end()) {
×
88
            _data.erase(it);
×
UNCOV
89
            removed_count++;
×
90
        }
91
    }
92

93
    if (removed_count > 0) {
×
94
        _sortData();// Re-sort after removals
×
UNCOV
95
        notifyObservers();
×
96
    }
97

UNCOV
98
    return removed_count;
×
99
}
100

101
void DigitalIntervalSeries::_setEventAtTime(TimeFrameIndex time, bool const event) {
×
102
    if (!event) {
×
UNCOV
103
        _removeEventAtTime(time);
×
104
    } else {
UNCOV
105
        _addEvent(Interval{time.getValue(), time.getValue()});
×
106
    }
UNCOV
107
}
×
108

109
void DigitalIntervalSeries::_removeEventAtTime(TimeFrameIndex const time) {
×
110
    for (auto it = _data.begin(); it != _data.end(); ++it) {
×
111
        if (is_contained(*it, time.getValue())) {
×
112
            if (time.getValue() == it->start && time.getValue() == it->end) {
×
113
                _data.erase(it);
×
114
            } else if (time.getValue() == it->start) {
×
115
                it->start = time.getValue() + 1;
×
116
            } else if (time.getValue() == it->end) {
×
UNCOV
117
                it->end = time.getValue() - 1;
×
118
            } else {
119
                auto preceding_event = Interval{it->start, time.getValue() - 1};
×
120
                auto following_event = Interval{time.getValue() + 1, it->end};
×
121
                _data.erase(it);
×
122
                _data.push_back(preceding_event);
×
UNCOV
123
                _data.push_back(following_event);
×
124

UNCOV
125
                _sortData();
×
126
            }
UNCOV
127
            return;
×
128
        }
129
    }
130
}
131

132
void DigitalIntervalSeries::_sortData() {
650✔
133
    std::sort(_data.begin(), _data.end());
650✔
134
}
650✔
135

136
void DigitalIntervalSeries::rebuildAllEntityIds() {
52✔
137
    if (!_identity_registry) {
52✔
138
        _entity_ids.assign(_data.size(), 0);
×
UNCOV
139
        return;
×
140
    }
141
    _entity_ids.clear();
52✔
142
    _entity_ids.reserve(_data.size());
52✔
143
    for (size_t i = 0; i < _data.size(); ++i) {
257✔
144
        // Use start as the discrete time index representative, and i as stable local index
145
        _entity_ids.push_back(
205✔
146
                _identity_registry->ensureId(_identity_data_key, EntityKind::IntervalEntity, TimeFrameIndex{_data[i].start}, static_cast<int>(i)));
205✔
147
    }
148
}
149

150
// ========== Entity Lookup Methods ==========
151

152
std::optional<Interval> DigitalIntervalSeries::getIntervalByEntityId(EntityId entity_id) const {
16✔
153
    if (!_identity_registry) {
16✔
154
        return std::nullopt;
×
155
    }
156

157
    auto descriptor = _identity_registry->get(entity_id);
16✔
158
    if (!descriptor || descriptor->kind != EntityKind::IntervalEntity || descriptor->data_key != _identity_data_key) {
16✔
159
        return std::nullopt;
×
160
    }
161

162
    int const local_index = descriptor->local_index;
16✔
163

164
    if (local_index < 0 || static_cast<size_t>(local_index) >= _data.size()) {
16✔
165
        return std::nullopt;
×
166
    }
167

168
    return _data[static_cast<size_t>(local_index)];
16✔
169
}
16✔
170

171
std::optional<int> DigitalIntervalSeries::getIndexByEntityId(EntityId entity_id) const {
6✔
172
    if (!_identity_registry) {
6✔
173
        return std::nullopt;
×
174
    }
175

176
    auto descriptor = _identity_registry->get(entity_id);
6✔
177
    if (!descriptor || descriptor->kind != EntityKind::IntervalEntity || descriptor->data_key != _identity_data_key) {
6✔
178
        return std::nullopt;
×
179
    }
180

181
    int const local_index = descriptor->local_index;
6✔
182

183
    if (local_index < 0 || static_cast<size_t>(local_index) >= _data.size()) {
6✔
184
        return std::nullopt;
×
185
    }
186

187
    return local_index;
6✔
188
}
6✔
189

190
std::vector<std::pair<EntityId, Interval>> DigitalIntervalSeries::getIntervalsByEntityIds(std::vector<EntityId> const & entity_ids) const {
1✔
191
    std::vector<std::pair<EntityId, Interval>> result;
1✔
192
    result.reserve(entity_ids.size());
1✔
193

194
    for (EntityId const entity_id: entity_ids) {
4✔
195
        auto interval = getIntervalByEntityId(entity_id);
3✔
196
        if (interval) {
3✔
197
            result.emplace_back(entity_id, *interval);
3✔
198
        }
199
    }
200

201
    return result;
1✔
202
}
×
203

204
std::vector<std::pair<EntityId, int>> DigitalIntervalSeries::getIndexInfoByEntityIds(std::vector<EntityId> const & entity_ids) const {
1✔
205
    std::vector<std::pair<EntityId, int>> result;
1✔
206
    result.reserve(entity_ids.size());
1✔
207

208
    for (EntityId const entity_id: entity_ids) {
4✔
209
        auto index = getIndexByEntityId(entity_id);
3✔
210
        if (index) {
3✔
211
            result.emplace_back(entity_id, *index);
3✔
212
        }
213
    }
214

215
    return result;
1✔
216
}
×
217

218
// ========== Intervals with EntityIDs ==========
219

220
std::vector<IntervalWithId> DigitalIntervalSeries::getIntervalsWithIdsInRange(TimeFrameIndex start_time, TimeFrameIndex stop_time) const {
156✔
221
    
222
    std::vector<IntervalWithId> result;
156✔
223
   // result.reserve(_data.size());// Reserve space for potential worst case
224

225
    for (size_t i = 0; i < _data.size(); ++i) {
715✔
226
        Interval const & interval = _data[i];
559✔
227
        // Check if interval overlaps with the range (using overlapping logic)
228
        if (interval.start <= stop_time.getValue() && interval.end >= start_time.getValue()) {
559✔
229
            EntityId const entity_id = (i < _entity_ids.size()) ? _entity_ids[i] : 0;
183✔
230
            result.emplace_back(interval, entity_id);
183✔
231
        }
232
    }
233
    return result;
156✔
UNCOV
234
}
×
235

236
std::vector<IntervalWithId> DigitalIntervalSeries::getIntervalsWithIdsInRange(TimeFrameIndex start_index,
156✔
237
                                                                              TimeFrameIndex stop_index,
238
                                                                              TimeFrame const * source_time_frame,
239
                                                                              TimeFrame const * interval_time_frame) const {
240
    if (source_time_frame == interval_time_frame) {
156✔
241
        return getIntervalsWithIdsInRange(start_index, stop_index);
82✔
242
    }
243

244
    // If either timeframe is null, fall back to original behavior
245
    if (!source_time_frame || !interval_time_frame) {
74✔
UNCOV
246
        return getIntervalsWithIdsInRange(start_index, stop_index);
×
247
    }
248

249
    auto [target_start_index, target_stop_index] = _convertTimeFrameRange(start_index, stop_index, source_time_frame, interval_time_frame);
74✔
250
    return getIntervalsWithIdsInRange(target_start_index, target_stop_index);
74✔
251
}
252

253
// ========== Helper Functions for Time Frame Conversion ==========
254

255
std::pair<TimeFrameIndex, TimeFrameIndex> DigitalIntervalSeries::_convertTimeFrameRange(
279✔
256
        TimeFrameIndex const start_index,
257
        TimeFrameIndex const stop_index,
258
        TimeFrame const * const source_time_frame,
259
        TimeFrame const * const target_time_frame) {
260

261
    // Get the time values from the source timeframe
262
    auto start_time_value = source_time_frame->getTimeAtIndex(start_index);
279✔
263
    auto stop_time_value = source_time_frame->getTimeAtIndex(stop_index);
279✔
264

265
    // Convert to indices in the target timeframe
266
    auto target_start_index = target_time_frame->getIndexAtTime(static_cast<float>(start_time_value), false);
279✔
267
    auto target_stop_index = target_time_frame->getIndexAtTime(static_cast<float>(stop_time_value));
279✔
268

269
    return {target_start_index, target_stop_index};
558✔
270
}
271

UNCOV
272
std::pair<int64_t, int64_t> DigitalIntervalSeries::_getTimeRangeFromIndices(
×
273
        TimeFrameIndex start_index,
274
        TimeFrameIndex stop_index) const {
275

UNCOV
276
    if (_time_frame) {
×
UNCOV
277
        auto start_time_value = _time_frame->getTimeAtIndex(start_index);
×
UNCOV
278
        auto stop_time_value = _time_frame->getTimeAtIndex(stop_index);
×
UNCOV
279
        return {static_cast<int64_t>(start_time_value), static_cast<int64_t>(stop_time_value)};
×
280
    } else {
281
        // Fallback to using indices as time values if no timeframe
UNCOV
282
        return {start_index.getValue(), stop_index.getValue()};
×
283
    }
284
}
285

UNCOV
286
int find_closest_preceding_event(DigitalIntervalSeries * digital_series, TimeFrameIndex time) {
×
UNCOV
287
    auto const & events = digital_series->getDigitalIntervalSeries();
×
288

289
    // Check if sorted
UNCOV
290
    for (size_t i = 1; i < events.size(); ++i) {
×
UNCOV
291
        if (events[i].start < events[i - 1].start) {
×
UNCOV
292
            throw std::runtime_error("DigitalIntervalSeries is not sorted");
×
293
        }
294
    }
UNCOV
295
    int closest_index = -1;
×
UNCOV
296
    for (size_t i = 0; i < events.size(); ++i) {
×
UNCOV
297
        if (events[i].start <= time.getValue()) {
×
UNCOV
298
            closest_index = static_cast<int>(i);
×
UNCOV
299
            if (time.getValue() <= events[i].end) {
×
UNCOV
300
                return static_cast<int>(i);
×
301
            }
302
        } else {
UNCOV
303
            break;
×
304
        }
305
    }
UNCOV
306
    return closest_index;
×
307
}
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