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

paulmthompson / WhiskerToolbox / 18914877332

29 Oct 2025 03:47PM UTC coverage: 73.148% (+0.1%) from 73.01%
18914877332

push

github

paulmthompson
added UI for interval peak detection

56798 of 77648 relevant lines covered (73.15%)

44492.79 hits per line

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

83.55
/src/DataManager/AnalogTimeSeries/Analog_Time_Series.cpp
1
#include "Analog_Time_Series.hpp"
2

3
#include <algorithm>
4
#include <cmath>// std::nan, std::sqrt
5
#include <iostream>
6
#include <numeric>// std::iota
7
#include <span>
8
#include <stdexcept>
9
#include <vector>
10

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

13
AnalogTimeSeries::AnalogTimeSeries()
22✔
14
    : _data(),
22✔
15
      _time_storage(DenseTimeRange(TimeFrameIndex(0), 0)) {}
44✔
16

17
AnalogTimeSeries::AnalogTimeSeries(std::map<int, float> analog_map)
81✔
18
    : _data(),
81✔
19
      _time_storage(DenseTimeRange(TimeFrameIndex(0), 0)) {
162✔
20
    setData(std::move(analog_map));
81✔
21
}
81✔
22

23
AnalogTimeSeries::AnalogTimeSeries(std::vector<float> analog_vector, std::vector<TimeFrameIndex> time_vector)
358✔
24
    : _data(),
358✔
25
      _time_storage(DenseTimeRange(TimeFrameIndex(0), 0)) {
716✔
26
    setData(std::move(analog_vector), std::move(time_vector));
358✔
27
}
358✔
28

29
AnalogTimeSeries::AnalogTimeSeries(std::vector<float> analog_vector, size_t num_samples)
165✔
30
    : _data(),
165✔
31
      _time_storage(DenseTimeRange(TimeFrameIndex(0), num_samples)) {
330✔
32
    if (analog_vector.size() != num_samples) {
165✔
33
        std::cerr << "Error: size of analog vector and number of samples are not the same!" << std::endl;
×
34
        return;
×
35
    }
36
    setData(std::move(analog_vector));
165✔
37
}
×
38

39
void AnalogTimeSeries::setData(std::vector<float> analog_vector) {
165✔
40
    _data = std::move(analog_vector);
165✔
41
    // Use dense time storage for consecutive indices starting from 0
42
    _time_storage = DenseTimeRange(TimeFrameIndex(0), _data.size());
165✔
43
}
165✔
44

45
void AnalogTimeSeries::setData(std::vector<float> analog_vector, std::vector<TimeFrameIndex> time_vector) {
358✔
46
    if (analog_vector.size() != time_vector.size()) {
358✔
47
        std::cerr << "Error: size of analog vector and time vector are not the same!" << std::endl;
×
48
        return;
×
49
    }
50

51
    _data = std::move(analog_vector);
358✔
52

53
    // Check if we can use dense storage (consecutive indices)
54
    bool is_dense = true;
358✔
55
    if (!time_vector.empty()) {
358✔
56
        TimeFrameIndex expected_value = time_vector[0];
354✔
57
        for (size_t i = 0; i < time_vector.size(); ++i) {
642,836✔
58
            if (time_vector[i] != expected_value + TimeFrameIndex(static_cast<int64_t>(i))) {
642,633✔
59
                is_dense = false;
151✔
60
                break;
151✔
61
            }
62
        }
63
    }
64

65
    if (is_dense && !time_vector.empty()) {
358✔
66
        _time_storage = DenseTimeRange(TimeFrameIndex(time_vector[0]), time_vector.size());
203✔
67
    } else {
68
        _time_storage = SparseTimeIndices(std::move(time_vector));
155✔
69
    }
70
}
71

72
void AnalogTimeSeries::setData(std::map<int, float> analog_map) {
81✔
73
    _data.clear();
81✔
74
    _data = std::vector<float>();
81✔
75
    auto time_storage = std::vector<TimeFrameIndex>();
81✔
76
    for (auto & [key, value]: analog_map) {
169✔
77
        time_storage.push_back(TimeFrameIndex(key));
88✔
78
        _data.push_back(value);
88✔
79
    }
80
    _time_storage = SparseTimeIndices(std::move(time_storage));
81✔
81
}
162✔
82

83
// ========== Overwriting Data ==========
84

85
void AnalogTimeSeries::overwriteAtTimeIndexes(std::vector<float> & analog_data, std::vector<TimeFrameIndex> & time_indices) {
4✔
86
    if (analog_data.size() != time_indices.size()) {
4✔
87
        std::cerr << "Analog data and time indices vectors must be the same size" << std::endl;
1✔
88
        return;
1✔
89
    }
90

91
    for (size_t i = 0; i < time_indices.size(); ++i) {
9✔
92
        // Find the DataArrayIndex that corresponds to this TimeFrameIndex
93
        std::optional<DataArrayIndex> data_index = findDataArrayIndexForTimeFrameIndex(time_indices[i]);
6✔
94

95
        // Only overwrite if we found a corresponding DataArrayIndex
96
        if (data_index.has_value()) {
6✔
97
            _data[data_index.value().getValue()] = analog_data[i];
4✔
98
        } else {
99
            std::cerr << "TimeFrameIndex " << time_indices[i].getValue() << " not found in time series" << std::endl;
2✔
100
        }
101
    }
102
}
103

104
void AnalogTimeSeries::overwriteAtDataArrayIndexes(std::vector<float> & analog_data, std::vector<DataArrayIndex> & data_indices) {
3✔
105
    if (analog_data.size() != data_indices.size()) {
3✔
106
        std::cerr << "Analog data and data indices vectors must be the same size" << std::endl;
1✔
107
        return;
1✔
108
    }
109

110
    for (size_t i = 0; i < data_indices.size(); ++i) {
6✔
111
        if (data_indices[i].getValue() < _data.size()) {
4✔
112
            _data[data_indices[i].getValue()] = analog_data[i];
2✔
113
        } else {
114
            std::cerr << "DataArrayIndex " << data_indices[i].getValue() << " is out of bounds (data size: " << _data.size() << ")" << std::endl;
2✔
115
        }
116
    }
117
}
118

119
// ========== Getting Data ==========
120

121
std::span<float const> AnalogTimeSeries::getDataInTimeFrameIndexRange(TimeFrameIndex start_time, TimeFrameIndex end_time) const {
1,161✔
122
    // Find the start and end indices using our boundary-finding methods
123
    auto start_index_opt = findDataArrayIndexGreaterOrEqual(start_time);
1,161✔
124
    auto end_index_opt = findDataArrayIndexLessOrEqual(end_time);
1,161✔
125

126
    // Check if both boundaries were found
127
    if (!start_index_opt.has_value() || !end_index_opt.has_value()) {
1,161✔
128
        // Return empty span if either boundary is not found
129
        return std::span<float const>();
34✔
130
    }
131

132
    size_t start_idx = start_index_opt.value().getValue();
1,127✔
133
    size_t end_idx = end_index_opt.value().getValue();
1,127✔
134

135
    // Validate that start <= end (should always be true if our logic is correct)
136
    if (start_idx > end_idx) {
1,127✔
137
        // Return empty span for invalid range
138
        return std::span<float const>();
51✔
139
    }
140

141
    // Calculate the size of the range (inclusive of both endpoints)
142
    size_t range_size = end_idx - start_idx + 1;
1,076✔
143

144
    // Return span from start_idx with range_size elements
145
    return std::span<float const>(_data.data() + start_idx, range_size);
1,076✔
146
}
147

148

149
[[nodiscard]] std::span<float const> AnalogTimeSeries::getDataInTimeFrameIndexRange(TimeFrameIndex start_time,
907✔
150
                                                                                    TimeFrameIndex end_time,
151
                                                                                    TimeFrame const * source_timeFrame) const {
152
    if (source_timeFrame == _time_frame.get()) {
907✔
153
        return getDataInTimeFrameIndexRange(start_time, end_time);
694✔
154
    }
155

156
    // If either timeframe is null, fall back to original behavior
157
    if (!source_timeFrame || !_time_frame) {
213✔
158
        return getDataInTimeFrameIndexRange(start_time, end_time);
×
159
    }
160

161
    // Convert the time index from source timeframe to target timeframe
162
    // 1. Get the time value from the source timeframe
163
    auto start_time_value = source_timeFrame->getTimeAtIndex(start_time);
213✔
164
    auto end_time_value = source_timeFrame->getTimeAtIndex(end_time);
213✔
165

166
    // 2. Convert that time value to an index in the analog timeframe
167
    auto target_start_index = _time_frame->getIndexAtTime(static_cast<float>(start_time_value), false);
213✔
168
    auto target_end_index = _time_frame->getIndexAtTime(static_cast<float>(end_time_value));
213✔
169

170
    // 3. Use the converted indices to get the data in the target timeframe
171
    return getDataInTimeFrameIndexRange(target_start_index, target_end_index);
213✔
172
}
173

174

175
// ========== TimeFrame Support ==========
176

177
std::optional<DataArrayIndex> AnalogTimeSeries::findDataArrayIndexForTimeFrameIndex(TimeFrameIndex time_index) const {
48✔
178
    return std::visit([time_index](auto const & time_storage) -> std::optional<DataArrayIndex> {
144✔
179
        if constexpr (std::is_same_v<std::decay_t<decltype(time_storage)>, DenseTimeRange>) {
180
            // For dense storage, check if the TimeFrameIndex falls within our range
181
            TimeFrameIndex start = time_storage.start_time_frame_index;
16✔
182
            auto end = TimeFrameIndex(start.getValue() + static_cast<int64_t>(time_storage.count) - 1);
16✔
183

184
            if (time_index >= start && time_index <= end) {
16✔
185
                // Calculate the DataArrayIndex
186
                auto offset = static_cast<size_t>(time_index.getValue() - start.getValue());
11✔
187
                return DataArrayIndex(offset);
11✔
188
            }
189
            return std::nullopt;
5✔
190
        } else {
191
            // For sparse storage, search for the TimeFrameIndex
192
            auto it = std::find(time_storage.time_frame_indices.begin(), time_storage.time_frame_indices.end(), time_index);
32✔
193
            if (it != time_storage.time_frame_indices.end()) {
32✔
194
                auto index = static_cast<size_t>(std::distance(time_storage.time_frame_indices.begin(), it));
22✔
195
                return DataArrayIndex(index);
22✔
196
            }
197
            return std::nullopt;
10✔
198
        }
199
    },
200
                      _time_storage);
96✔
201
}
202

203
std::optional<DataArrayIndex> AnalogTimeSeries::findDataArrayIndexGreaterOrEqual(TimeFrameIndex target_time) const {
1,331✔
204
    return std::visit([target_time](auto const & time_storage) -> std::optional<DataArrayIndex> {
3,993✔
205
        if constexpr (std::is_same_v<std::decay_t<decltype(time_storage)>, DenseTimeRange>) {
206
            // For dense storage, calculate the position if target_time falls within our range
207
            TimeFrameIndex start = time_storage.start_time_frame_index;
1,208✔
208
            auto end = TimeFrameIndex(start.getValue() + static_cast<int64_t>(time_storage.count) - 1);
1,208✔
209

210
            if (target_time <= end) {
1,208✔
211
                // Find the first index >= target_time
212
                TimeFrameIndex effective_start = (target_time >= start) ? target_time : start;
1,189✔
213
                auto offset = static_cast<size_t>(effective_start.getValue() - start.getValue());
1,189✔
214
                return DataArrayIndex(offset);
1,189✔
215
            }
216
            return std::nullopt;
19✔
217
        } else {
218
            // For sparse storage, use lower_bound to find first element >= target_time
219
            auto it = std::lower_bound(time_storage.time_frame_indices.begin(), time_storage.time_frame_indices.end(), target_time);
123✔
220
            if (it != time_storage.time_frame_indices.end()) {
123✔
221
                auto index = static_cast<size_t>(std::distance(time_storage.time_frame_indices.begin(), it));
108✔
222
                return DataArrayIndex(index);
108✔
223
            }
224
            return std::nullopt;
15✔
225
        }
226
    },
227
                      _time_storage);
2,662✔
228
}
229

230
std::optional<DataArrayIndex> AnalogTimeSeries::findDataArrayIndexLessOrEqual(TimeFrameIndex target_time) const {
1,331✔
231
    return std::visit([target_time](auto const & time_storage) -> std::optional<DataArrayIndex> {
3,993✔
232
        if constexpr (std::is_same_v<std::decay_t<decltype(time_storage)>, DenseTimeRange>) {
233
            // For dense storage, calculate the position if target_time falls within our range
234
            TimeFrameIndex start = time_storage.start_time_frame_index;
1,208✔
235
            auto end = TimeFrameIndex(start.getValue() + static_cast<int64_t>(time_storage.count) - 1);
1,208✔
236

237
            if (target_time >= start) {
1,208✔
238
                // Find the last index <= target_time
239
                TimeFrameIndex effective_end = (target_time <= end) ? target_time : end;
1,180✔
240
                auto offset = static_cast<size_t>(effective_end.getValue() - start.getValue());
1,180✔
241
                return DataArrayIndex(offset);
1,180✔
242
            }
243
            return std::nullopt;
28✔
244
        } else {
245
            // For sparse storage, use upper_bound to find first element > target_time, then step back
246
            auto it = std::upper_bound(time_storage.time_frame_indices.begin(), time_storage.time_frame_indices.end(), target_time);
123✔
247
            if (it != time_storage.time_frame_indices.begin()) {
123✔
248
                --it;// Step back to get the last element <= target_time
121✔
249
                auto index = static_cast<size_t>(std::distance(time_storage.time_frame_indices.begin(), it));
121✔
250
                return DataArrayIndex(index);
121✔
251
            }
252
            return std::nullopt;
2✔
253
        }
254
    },
255
                      _time_storage);
2,662✔
256
}
257

258
// ========== Time-Value Range Access Implementation ==========
259

260
AnalogTimeSeries::TimeValueRangeIterator::TimeValueRangeIterator(AnalogTimeSeries const * series, DataArrayIndex start_index, DataArrayIndex end_index, bool is_end)
11✔
261
    : _series(series),
11✔
262
      _current_index(is_end ? end_index : start_index),
11✔
263
      _end_index(end_index),
11✔
264
      _is_end(is_end) {
11✔
265
    if (!_is_end && _current_index.getValue() < _end_index.getValue()) {
11✔
266
        _updateCurrentPoint();
5✔
267
    }
268
}
11✔
269

270
AnalogTimeSeries::TimeValueRangeIterator::reference AnalogTimeSeries::TimeValueRangeIterator::operator*() const {
15✔
271
    if (_is_end || _current_index.getValue() >= _end_index.getValue()) {
15✔
272
        throw std::out_of_range("TimeValueRangeIterator: attempt to dereference end iterator");
×
273
    }
274
    _updateCurrentPoint();
15✔
275
    return _current_point;
15✔
276
}
277

278
AnalogTimeSeries::TimeValueRangeIterator::pointer AnalogTimeSeries::TimeValueRangeIterator::operator->() const {
4✔
279
    return &(operator*());
4✔
280
}
281

282
AnalogTimeSeries::TimeValueRangeIterator & AnalogTimeSeries::TimeValueRangeIterator::operator++() {
12✔
283
    if (_is_end || _current_index.getValue() >= _end_index.getValue()) {
12✔
284
        _is_end = true;
×
285
        return *this;
×
286
    }
287

288
    _current_index = DataArrayIndex(_current_index.getValue() + 1);
12✔
289

290
    if (_current_index.getValue() >= _end_index.getValue()) {
12✔
291
        _is_end = true;
5✔
292
    }
293

294
    return *this;
12✔
295
}
296

297
AnalogTimeSeries::TimeValueRangeIterator AnalogTimeSeries::TimeValueRangeIterator::operator++(int) {
1✔
298
    auto temp = *this;
1✔
299
    ++(*this);
1✔
300
    return temp;
1✔
301
}
302

303
bool AnalogTimeSeries::TimeValueRangeIterator::operator==(TimeValueRangeIterator const & other) const {
16✔
304
    return _series == other._series &&
16✔
305
           _current_index.getValue() == other._current_index.getValue() &&
32✔
306
           _is_end == other._is_end;
21✔
307
}
308

309
bool AnalogTimeSeries::TimeValueRangeIterator::operator!=(TimeValueRangeIterator const & other) const {
14✔
310
    return !(*this == other);
14✔
311
}
312

313
void AnalogTimeSeries::TimeValueRangeIterator::_updateCurrentPoint() const {
20✔
314
    if (_is_end || _current_index.getValue() >= _end_index.getValue()) {
20✔
315
        return;
×
316
    }
317

318
    _current_point = TimeValuePoint(
40✔
319
            _series->getTimeFrameIndexAtDataArrayIndex(_current_index),
20✔
320
            _series->getDataAtDataArrayIndex(_current_index));
20✔
321
}
322

323
AnalogTimeSeries::TimeValueRangeView::TimeValueRangeView(AnalogTimeSeries const * series, DataArrayIndex start_index, DataArrayIndex end_index)
6✔
324
    : _series(series),
6✔
325
      _start_index(start_index),
6✔
326
      _end_index(end_index) {}
6✔
327

328
AnalogTimeSeries::TimeValueRangeIterator AnalogTimeSeries::TimeValueRangeView::begin() const {
5✔
329
    return {_series, _start_index, _end_index, false};
5✔
330
}
331

332
AnalogTimeSeries::TimeValueRangeIterator AnalogTimeSeries::TimeValueRangeView::end() const {
6✔
333
    return {_series, _start_index, _end_index, true};
6✔
334
}
335

336
size_t AnalogTimeSeries::TimeValueRangeView::size() const {
4✔
337
    if (_start_index.getValue() >= _end_index.getValue()) {
4✔
338
        return 0;
2✔
339
    }
340
    return _end_index.getValue() - _start_index.getValue();
2✔
341
}
342

343
bool AnalogTimeSeries::TimeValueRangeView::empty() const {
2✔
344
    return size() == 0;
2✔
345
}
346

347
// Dense and Sparse time index iterators for TimeIndexRange
348
namespace {
349
// Dense time index iterator implementation
350
class DenseTimeIndexIterator : public AnalogTimeSeries::TimeIndexIterator {
351
public:
352
    DenseTimeIndexIterator(TimeFrameIndex start_time, DataArrayIndex current_offset, DataArrayIndex end_offset, bool is_end)
80✔
353
        : _start_time(start_time),
160✔
354
          _current_offset(current_offset),
80✔
355
          _end_offset(end_offset),
80✔
356
          _current_value(TimeFrameIndex(0)),
80✔
357
          _is_end(is_end) {
80✔
358
        if (!_is_end) {
80✔
359
            _current_value = TimeFrameIndex(_start_time.getValue() + static_cast<int64_t>(_current_offset.getValue()));
80✔
360
        }
361
    }
80✔
362

363
    reference operator*() const override {
27,976✔
364
        if (_is_end || _current_offset.getValue() >= _end_offset.getValue()) {
27,976✔
365
            throw std::out_of_range("DenseTimeIndexIterator: attempt to dereference end iterator");
×
366
        }
367
        return _current_value;
27,976✔
368
    }
369

370
    TimeIndexIterator & operator++() override {
9,376✔
371
        if (_is_end || _current_offset.getValue() >= _end_offset.getValue()) {
9,376✔
372
            _is_end = true;
×
373
            return *this;
×
374
        }
375

376
        _current_offset = DataArrayIndex(_current_offset.getValue() + 1);
9,376✔
377

378
        if (_current_offset.getValue() >= _end_offset.getValue()) {
9,376✔
379
            _is_end = true;
75✔
380
        } else {
381
            _current_value = TimeFrameIndex(_start_time.getValue() + static_cast<int64_t>(_current_offset.getValue()));
9,301✔
382
        }
383

384
        return *this;
9,376✔
385
    }
386

387
    bool operator==(TimeIndexIterator const & other) const override {
×
388
        auto const * other_dense = dynamic_cast<DenseTimeIndexIterator const *>(&other);
×
389
        if (!other_dense) return false;
×
390

391
        return _start_time.getValue() == other_dense->_start_time.getValue() &&
×
392
               _current_offset.getValue() == other_dense->_current_offset.getValue() &&
×
393
               _is_end == other_dense->_is_end;
×
394
    }
395

396
    bool operator!=(TimeIndexIterator const & other) const override {
×
397
        return !(*this == other);
×
398
    }
399

400
    std::unique_ptr<TimeIndexIterator> clone() const override {
×
401
        return std::make_unique<DenseTimeIndexIterator>(_start_time, _current_offset, _end_offset, _is_end);
×
402
    }
403

404
private:
405
    TimeFrameIndex _start_time;
406
    DataArrayIndex _current_offset;
407
    DataArrayIndex _end_offset;
408
    mutable TimeFrameIndex _current_value;
409
    bool _is_end;
410
};
411

412
// Sparse time index iterator implementation
413
class SparseTimeIndexIterator : public AnalogTimeSeries::TimeIndexIterator {
414
public:
415
    SparseTimeIndexIterator(std::vector<TimeFrameIndex> const * time_indices, DataArrayIndex current_index, DataArrayIndex end_index, bool is_end)
24✔
416
        : _time_indices(time_indices),
48✔
417
          _current_index(current_index),
24✔
418
          _end_index(end_index),
24✔
419
          _is_end(is_end) {}
24✔
420

421
    reference operator*() const override {
26✔
422
        if (_is_end || _current_index.getValue() >= _end_index.getValue()) {
26✔
423
            throw std::out_of_range("SparseTimeIndexIterator: attempt to dereference end iterator");
×
424
        }
425
        return (*_time_indices)[_current_index.getValue()];
26✔
426
    }
427

428
    TimeIndexIterator & operator++() override {
22✔
429
        if (_is_end || _current_index.getValue() >= _end_index.getValue()) {
22✔
430
            _is_end = true;
×
431
            return *this;
×
432
        }
433

434
        _current_index = DataArrayIndex(_current_index.getValue() + 1);
22✔
435

436
        if (_current_index.getValue() >= _end_index.getValue()) {
22✔
437
            _is_end = true;
×
438
        }
439

440
        return *this;
22✔
441
    }
442

443
    bool operator==(TimeIndexIterator const & other) const override {
×
444
        auto const * other_sparse = dynamic_cast<SparseTimeIndexIterator const *>(&other);
×
445
        if (!other_sparse) return false;
×
446

447
        return _time_indices == other_sparse->_time_indices &&
×
448
               _current_index.getValue() == other_sparse->_current_index.getValue() &&
×
449
               _is_end == other_sparse->_is_end;
×
450
    }
451

452
    bool operator!=(TimeIndexIterator const & other) const override {
×
453
        return !(*this == other);
×
454
    }
455

456
    [[nodiscard]] std::unique_ptr<TimeIndexIterator> clone() const override {
×
457
        return std::make_unique<SparseTimeIndexIterator>(_time_indices, _current_index, _end_index, _is_end);
×
458
    }
459

460
private:
461
    std::vector<TimeFrameIndex> const * _time_indices;
462
    DataArrayIndex _current_index;
463
    DataArrayIndex _end_index;
464
    bool _is_end;
465
};
466
}// namespace
467

468
AnalogTimeSeries::TimeIndexRange::TimeIndexRange(AnalogTimeSeries const * series, DataArrayIndex start_index, DataArrayIndex end_index)
146✔
469
    : _series(series),
146✔
470
      _start_index(start_index),
146✔
471
      _end_index(end_index) {}
146✔
472

473
std::unique_ptr<AnalogTimeSeries::TimeIndexIterator> AnalogTimeSeries::TimeIndexRange::begin() const {
104✔
474
    return std::visit([this](auto const & time_storage) -> std::unique_ptr<TimeIndexIterator> {
208✔
475
        if constexpr (std::is_same_v<std::decay_t<decltype(time_storage)>, DenseTimeRange>) {
476
            // Dense storage - create iterator based on start time and offset
477
            return std::make_unique<DenseTimeIndexIterator>(
478
                    time_storage.start_time_frame_index,
80✔
479
                    _start_index,
80✔
480
                    _end_index,
80✔
481
                    false);
80✔
482
        } else {
483
            // Sparse storage - create iterator with direct index access
484
            return std::make_unique<SparseTimeIndexIterator>(
485
                    &time_storage.time_frame_indices,
24✔
486
                    _start_index,
24✔
487
                    _end_index,
24✔
488
                    false);
48✔
489
        }
490
    },
491
                      _series->getTimeStorage());
208✔
492
}
493

494
std::unique_ptr<AnalogTimeSeries::TimeIndexIterator> AnalogTimeSeries::TimeIndexRange::end() const {
×
495
    return std::visit([this](auto const & time_storage) -> std::unique_ptr<TimeIndexIterator> {
×
496
        if constexpr (std::is_same_v<std::decay_t<decltype(time_storage)>, DenseTimeRange>) {
497
            // Dense storage - create end iterator
498
            return std::make_unique<DenseTimeIndexIterator>(
499
                    time_storage.start_time_frame_index,
×
500
                    _start_index,
×
501
                    _end_index,
×
502
                    true);
×
503
        } else {
504
            // Sparse storage - create end iterator
505
            return std::make_unique<SparseTimeIndexIterator>(
506
                    &time_storage.time_frame_indices,
×
507
                    _start_index,
×
508
                    _end_index,
×
509
                    true);
×
510
        }
511
    },
512
                      _series->getTimeStorage());
×
513
}
514

515
size_t AnalogTimeSeries::TimeIndexRange::size() const {
9✔
516
    if (_start_index.getValue() >= _end_index.getValue()) {
9✔
517
        return 0;
2✔
518
    }
519
    return _end_index.getValue() - _start_index.getValue();
7✔
520
}
521

522
bool AnalogTimeSeries::TimeIndexRange::empty() const {
3✔
523
    return size() == 0;
3✔
524
}
525

526
AnalogTimeSeries::TimeValueSpanPair::TimeValueSpanPair(std::span<float const> data_span, AnalogTimeSeries const * series, DataArrayIndex start_index, DataArrayIndex end_index)
146✔
527
    : values(data_span),
146✔
528
      time_indices(series, start_index, end_index) {}
146✔
529

530
AnalogTimeSeries::TimeValueRangeView AnalogTimeSeries::getTimeValueRangeInTimeFrameIndexRange(TimeFrameIndex start_time, TimeFrameIndex end_time) const {
6✔
531
    // Use existing boundary-finding logic
532
    auto start_index_opt = findDataArrayIndexGreaterOrEqual(start_time);
6✔
533
    auto end_index_opt = findDataArrayIndexLessOrEqual(end_time);
6✔
534

535
    // Handle cases where boundaries are not found
536
    if (!start_index_opt.has_value() || !end_index_opt.has_value()) {
6✔
537
        // Return empty range
538
        return {this, DataArrayIndex(0), DataArrayIndex(0)};
1✔
539
    }
540

541
    size_t start_idx = start_index_opt.value().getValue();
5✔
542
    size_t end_idx = end_index_opt.value().getValue();
5✔
543

544
    // Validate that start <= end
545
    if (start_idx > end_idx) {
5✔
546
        // Return empty range for invalid range
547
        return {this, DataArrayIndex(0), DataArrayIndex(0)};
×
548
    }
549

550
    // Create range view (end_idx + 1 because end is exclusive for the range)
551
    return {this, DataArrayIndex(start_idx), DataArrayIndex(end_idx + 1)};
5✔
552
}
553

554
AnalogTimeSeries::TimeValueSpanPair AnalogTimeSeries::getTimeValueSpanInTimeFrameIndexRange(TimeFrameIndex start_time, TimeFrameIndex end_time) const {
146✔
555
    // Use existing getDataInTimeFrameIndexRange for the span
556
    auto data_span = getDataInTimeFrameIndexRange(start_time, end_time);
146✔
557

558
    // Find the corresponding array indices for the time iterator
559
    auto start_index_opt = findDataArrayIndexGreaterOrEqual(start_time);
146✔
560
    auto end_index_opt = findDataArrayIndexLessOrEqual(end_time);
146✔
561

562
    // Handle cases where boundaries are not found
563
    if (!start_index_opt.has_value() || !end_index_opt.has_value()) {
146✔
564
        // Return empty span pair
565
        return {std::span<float const>(), this, DataArrayIndex(0), DataArrayIndex(0)};
21✔
566
    }
567

568
    size_t start_idx = start_index_opt.value().getValue();
125✔
569
    size_t end_idx = end_index_opt.value().getValue();
125✔
570

571
    // Validate that start <= end
572
    if (start_idx > end_idx) {
125✔
573
        // Return empty span pair for invalid range
574
        return {std::span<float const>(), this, DataArrayIndex(0), DataArrayIndex(0)};
×
575
    }
576

577
    // Create span pair (end_idx + 1 because end is exclusive for the range)
578
    return {data_span, this, DataArrayIndex(start_idx), DataArrayIndex(end_idx + 1)};
125✔
579
}
580

581
AnalogTimeSeries::TimeValueSpanPair AnalogTimeSeries::getTimeValueSpanInTimeFrameIndexRange(
2✔
582
        TimeFrameIndex start_time,
583
        TimeFrameIndex end_time,
584
        TimeFrame const * source_timeFrame) const {
585
    
586
    if (source_timeFrame == _time_frame.get()) {
2✔
587
        return getTimeValueSpanInTimeFrameIndexRange(start_time, end_time);
1✔
588
    }
589

590
    // If either timeframe is null, fall back to original behavior
591
    if (!source_timeFrame || !_time_frame) {
1✔
592
        return getTimeValueSpanInTimeFrameIndexRange(start_time, end_time);
×
593
    }
594

595
    // Convert the time index from source timeframe to target timeframe
596
    // 1. Get the time value from the source timeframe
597
    auto start_time_value = source_timeFrame->getTimeAtIndex(start_time);
1✔
598
    auto end_time_value = source_timeFrame->getTimeAtIndex(end_time);
1✔
599

600
    // 2. Convert that time value to an index in the analog timeframe
601
    auto target_start_index = _time_frame->getIndexAtTime(static_cast<float>(start_time_value), false);
1✔
602
    auto target_end_index = _time_frame->getIndexAtTime(static_cast<float>(end_time_value));
1✔
603

604
    // 3. Use the converted indices to get the data in the target timeframe
605
    return getTimeValueSpanInTimeFrameIndexRange(target_start_index, target_end_index);
1✔
606
}
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