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

paulmthompson / WhiskerToolbox / 15909500151

26 Jun 2025 06:20PM UTC coverage: 71.617% (-0.03%) from 71.644%
15909500151

push

github

paulmthompson
scrollbar updates video time

11309 of 15791 relevant lines covered (71.62%)

1134.15 hits per line

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

97.3
/src/WhiskerToolbox/DataManager/TimeFrame/TimeFrameV2.hpp
1
#ifndef TIMEFRAME_V2_HPP
2
#define TIMEFRAME_V2_HPP
3

4
#include "StrongTimeTypes.hpp"
5
#include "TimeFrame.hpp"
6

7
#include <algorithm>
8
#include <cstdint>
9
#include <memory>
10
#include <optional>
11
#include <ranges>
12
#include <variant>
13
#include <vector>
14

15
// Forward declarations for filename-based creation
16
enum class FilenameTimeFrameMode;
17
struct FilenameTimeFrameOptions;
18

19
/**
20
 * @brief Dense time range representation using std::ranges::iota_view
21
 * 
22
 * Represents a regular sequence of time values without storing them explicitly.
23
 * Useful for dense sampling scenarios like electrophysiology data.
24
 */
25
struct DenseTimeRange {
26
    int64_t start;
27
    int64_t count;
28
    int64_t step;
29

30
    DenseTimeRange(int64_t start_val, int64_t count_val, int64_t step_val = 1)
27✔
31
        : start(start_val),
27✔
32
          count(count_val),
27✔
33
          step(step_val) {}
27✔
34

35
    /**
36
     * @brief Get the time value at a specific index
37
     * 
38
     * @param index Index into the dense range
39
     * @return Time value at the given index
40
     */
41
    [[nodiscard]] int64_t getTimeAtIndex(TimeFrameIndex index) const {
28✔
42
        if (index.getValue() < 0 || index.getValue() >= count) {
28✔
43
            return start;// Return start value for out-of-bounds access
2✔
44
        }
45
        return start + index.getValue() * step;
26✔
46
    }
47

48
    /**
49
     * @brief Find the index closest to a given time value
50
     * 
51
     * @param time_value Time value to search for
52
     * @return Index of the closest time value
53
     */
54
    [[nodiscard]] TimeFrameIndex getIndexAtTime(double time_value) const {
11✔
55
        if (count == 0) return TimeFrameIndex(0);
11✔
56

57
        TimeFrameIndex index = TimeFrameIndex(static_cast<int64_t>((time_value - start) / step));
10✔
58

59
        // Clamp to valid range
60
        if (index.getValue() < 0) return TimeFrameIndex(0);
10✔
61
        if (index.getValue() >= count) return TimeFrameIndex(count - 1);
9✔
62

63
        return index;
8✔
64
    }
65

66
    /**
67
     * @brief Get the total number of time points
68
     */
69
    [[nodiscard]] int64_t size() const { return count; }
14✔
70
};
71

72
/**
73
 * @brief Storage variant for TimeFrame data
74
 * 
75
 * Can hold either sparse time data (std::vector) or dense time data (DenseTimeRange).
76
 */
77
using TimeStorage = std::variant<std::vector<int64_t>, DenseTimeRange>;
78

79
/**
80
 * @brief Enhanced TimeFrame with variant storage and strong type support
81
 * 
82
 * This is the new TimeFrame implementation that supports both sparse and dense
83
 * time storage, strong typing for different coordinate systems, and efficient
84
 * memory usage for regular sampling patterns.
85
 */
86
template<typename CoordinateType>
87
class TimeFrameV2 {
88
    using TimeStorage = std::variant<std::vector<int64_t>, DenseTimeRange>;
89

90
public:
91
    // Type alias to expose template parameter
92
    using coordinate_type = CoordinateType;
93
    /**
94
     * @brief Create a TimeFrame from a vector of time values (sparse storage)
95
     * 
96
     * @param times Vector of time values
97
     * @param sampling_rate_hz Optional sampling rate in Hz (for ClockTicks conversion to seconds)
98
     */
99
    explicit TimeFrameV2(std::vector<int64_t> times, std::optional<double> sampling_rate_hz = std::nullopt)
15✔
100
        : _storage(std::move(times)),
15✔
101
          _sampling_rate_hz(sampling_rate_hz) {}
15✔
102

103
    /**
104
     * @brief Create a TimeFrame with dense storage
105
     * 
106
     * @param start Starting time value
107
     * @param count Number of time points
108
     * @param step Step size between consecutive time points
109
     * @param sampling_rate_hz Optional sampling rate in Hz (for ClockTicks conversion to seconds)
110
     */
111
    TimeFrameV2(int64_t start, int64_t count, int64_t step = 1, std::optional<double> sampling_rate_hz = std::nullopt)
23✔
112
        : _storage(DenseTimeRange(start, count, step)),
23✔
113
          _sampling_rate_hz(sampling_rate_hz) {}
23✔
114

115
    /**
116
     * @brief Get the time value at a specific index
117
     * 
118
     * @param index Index into the time frame
119
     * @return Time coordinate at the given index
120
     */
121
    [[nodiscard]] CoordinateType getTimeAtIndex(TimeFrameIndex index) const {
36✔
122
        return std::visit([index](auto const & storage) -> CoordinateType {
108✔
123
            if constexpr (std::is_same_v<std::decay_t<decltype(storage)>, std::vector<int64_t>>) {
124
                if (index.getValue() < 0 || static_cast<size_t>(index.getValue()) >= storage.size()) {
16✔
125
                    return CoordinateType(0);// Return zero for out-of-bounds
2✔
126
                }
127
                return CoordinateType(storage[static_cast<size_t>(index.getValue())]);
14✔
128
            } else {// DenseTimeRange
129
                return CoordinateType(storage.getTimeAtIndex(index));
20✔
130
            }
131
        },
132
                          _storage);
72✔
133
    }
134

135
    /**
136
     * @brief Find the index closest to a given time value
137
     * 
138
     * @param time_value Time value to search for (in the same coordinate system)
139
     * @return Index of the closest time value
140
     */
141
    [[nodiscard]] TimeFrameIndex getIndexAtTime(CoordinateType time_value) const {
14✔
142
        return std::visit([time_value](auto const & storage) -> TimeFrameIndex {
42✔
143
            if constexpr (std::is_same_v<std::decay_t<decltype(storage)>, std::vector<int64_t>>) {
144
                // Binary search for sparse storage
145
                double target = static_cast<double>(time_value.getValue());
11✔
146
                auto it = std::lower_bound(storage.begin(), storage.end(), target);
11✔
147

148
                if (it == storage.end()) {
11✔
149
                    return TimeFrameIndex(static_cast<int64_t>(storage.size()) - 1);
1✔
150
                }
151
                if (it == storage.begin()) {
10✔
152
                    return TimeFrameIndex(0);
4✔
153
                }
154

155
                // Find closest value
156
                auto prev = it - 1;
6✔
157
                if (std::abs(static_cast<double>(*prev) - target) <= std::abs(static_cast<double>(*it) - target)) {
6✔
158
                    return TimeFrameIndex(static_cast<int64_t>(std::distance(storage.begin(), prev)));
2✔
159
                } else {
160
                    return TimeFrameIndex(static_cast<int64_t>(std::distance(storage.begin(), it)));
10✔
161
                }
162
            } else {// DenseTimeRange
163
                return storage.getIndexAtTime(static_cast<double>(time_value.getValue()));
3✔
164
            }
165
        },
166
                          _storage);
28✔
167
    }
168

169
    /**
170
     * @brief Get the total number of time points
171
     */
172
    [[nodiscard]] int64_t getTotalFrameCount() const {
20✔
173
        return std::visit([](auto const & storage) -> int64_t {
40✔
174
            if constexpr (std::is_same_v<std::decay_t<decltype(storage)>, std::vector<int64_t>>) {
175
                return static_cast<int64_t>(storage.size());
8✔
176
            } else {// DenseTimeRange
177
                return storage.size();
12✔
178
            }
179
        },
180
                          _storage);
40✔
181
    }
182

183
    /**
184
     * @brief Check if an index is within bounds and clamp if necessary
185
     * 
186
     * @param index Index to check
187
     * @return Clamped index within valid range
188
     */
189
    [[nodiscard]] TimeFrameIndex checkIndexInBounds(TimeFrameIndex index) const {
3✔
190
        int64_t total_count = getTotalFrameCount();
3✔
191
        if (index.getValue() < 0) return TimeFrameIndex(0);
3✔
192
        if (index.getValue() >= total_count) return TimeFrameIndex(total_count - 1);
2✔
193
        return TimeFrameIndex(index.getValue());
1✔
194
    }
195

196
    /**
197
     * @brief Get the sampling rate (if available)
198
     * 
199
     * Only meaningful for ClockTicks coordinate types.
200
     * 
201
     * @return Sampling rate in Hz, or nullopt if not set
202
     */
203
    [[nodiscard]] std::optional<double> getSamplingRate() const { return _sampling_rate_hz; }
5✔
204

205
    /**
206
     * @brief Convert coordinate to seconds (if sampling rate is available)
207
     * 
208
     * Only works for ClockTicks coordinate types with a known sampling rate.
209
     * 
210
     * @param coord Coordinate to convert
211
     * @return Time in seconds, or nullopt if conversion is not possible
212
     */
213
    [[nodiscard]] std::optional<Seconds> coordinateToSeconds(CoordinateType coord) const {
2✔
214
        if constexpr (std::is_same_v<CoordinateType, ClockTicks>) {
215
            if (_sampling_rate_hz.has_value()) {
2✔
216
                return Seconds(static_cast<double>(coord.getValue()) / _sampling_rate_hz.value());
2✔
217
            }
218
        }
219
        return std::nullopt;
×
220
    }
221

222
    /**
223
     * @brief Convert seconds to coordinate (if sampling rate is available)
224
     * 
225
     * Only works for ClockTicks coordinate types with a known sampling rate.
226
     * 
227
     * @param seconds Time in seconds
228
     * @return Coordinate value, or nullopt if conversion is not possible
229
     */
230
    [[nodiscard]] std::optional<CoordinateType> secondsToCoordinate(Seconds seconds) const {
1✔
231
        if constexpr (std::is_same_v<CoordinateType, ClockTicks>) {
232
            if (_sampling_rate_hz.has_value()) {
1✔
233
                int64_t ticks = static_cast<int64_t>(seconds.getValue() * _sampling_rate_hz.value());
1✔
234
                return CoordinateType(ticks);
1✔
235
            }
236
        }
237
        return std::nullopt;
×
238
    }
239

240
    /**
241
     * @brief Check if this TimeFrame uses dense storage
242
     */
243
    [[nodiscard]] bool isDense() const {
10✔
244
        return std::holds_alternative<DenseTimeRange>(_storage);
10✔
245
    }
246

247
    /**
248
     * @brief Check if this TimeFrame uses sparse storage
249
     */
250
    [[nodiscard]] bool isSparse() const {
9✔
251
        return std::holds_alternative<std::vector<int64_t>>(_storage);
9✔
252
    }
253

254
private:
255
    TimeStorage _storage;
256
    std::optional<double> _sampling_rate_hz;
257
};
258

259
// Type aliases for common TimeFrame types
260
using CameraTimeFrame = TimeFrameV2<CameraFrameIndex>;
261
using ClockTimeFrame = TimeFrameV2<ClockTicks>;
262
using SecondsTimeFrame = TimeFrameV2<Seconds>;
263
using UncalibratedTimeFrame = TimeFrameV2<UncalibratedIndex>;
264

265
/**
266
 * @brief Variant type that can hold any TimeFrame type
267
 */
268
using AnyTimeFrame = std::variant<
269
        std::shared_ptr<CameraTimeFrame>,
270
        std::shared_ptr<ClockTimeFrame>,
271
        std::shared_ptr<SecondsTimeFrame>,
272
        std::shared_ptr<UncalibratedTimeFrame>>;
273

274
/**
275
 * @brief Utility functions for TimeFrame operations
276
 */
277
namespace TimeFrameUtils {
278

279
/**
280
     * @brief Create a dense ClockTimeFrame for regular sampling
281
     * 
282
     * @param start_tick Starting tick value
283
     * @param num_samples Number of samples
284
     * @param sampling_rate_hz Sampling rate in Hz
285
     * @return Shared pointer to the created ClockTimeFrame
286
     */
287
[[nodiscard]] inline std::shared_ptr<ClockTimeFrame> createDenseClockTimeFrame(
8✔
288
        int64_t start_tick, int64_t num_samples, double sampling_rate_hz) {
289
    return std::make_shared<ClockTimeFrame>(start_tick, num_samples, 1, sampling_rate_hz);
16✔
290
}
291

292
/**
293
     * @brief Create a sparse ClockTimeFrame from tick indices
294
     * 
295
     * @param tick_indices Vector of clock tick indices
296
     * @param sampling_rate_hz Sampling rate in Hz
297
     * @return Shared pointer to the created ClockTimeFrame
298
     */
299
[[nodiscard]] inline std::shared_ptr<ClockTimeFrame> createSparseClockTimeFrame(
1✔
300
        std::vector<int64_t> tick_indices, double sampling_rate_hz) {
301
    return std::make_shared<ClockTimeFrame>(std::move(tick_indices), sampling_rate_hz);
1✔
302
}
303

304
/**
305
     * @brief Create a sparse CameraTimeFrame from frame indices
306
     * 
307
     * @param frame_indices Vector of camera frame indices
308
     * @return Shared pointer to the created CameraTimeFrame
309
     */
310
[[nodiscard]] inline std::shared_ptr<CameraTimeFrame> createSparseCameraTimeFrame(
6✔
311
        std::vector<int64_t> frame_indices) {
312
    return std::make_shared<CameraTimeFrame>(std::move(frame_indices));
6✔
313
}
314

315
/**
316
     * @brief Create a dense CameraTimeFrame for regular frame capture
317
     * 
318
     * @param start_frame Starting frame index
319
     * @param num_frames Number of frames
320
     * @return Shared pointer to the created CameraTimeFrame
321
     */
322
[[nodiscard]] inline std::shared_ptr<CameraTimeFrame> createDenseCameraTimeFrame(
2✔
323
        int64_t start_frame, int64_t num_frames) {
324
    return std::make_shared<CameraTimeFrame>(start_frame, num_frames, 1);
4✔
325
}
326

327
// ========== Filename-based TimeFrameV2 Creation ==========
328

329
/**
330
     * @brief Create a CameraTimeFrame from image folder filenames
331
     * 
332
     * This function is the TimeFrameV2 equivalent of createTimeFrameFromFilenames.
333
     * It extracts frame indices from filenames and creates a CameraTimeFrame.
334
     * 
335
     * @param options Configuration options (same as legacy version)
336
     * @return A shared pointer to the created CameraTimeFrame, or nullptr on failure
337
     */
338
[[nodiscard]] std::shared_ptr<CameraTimeFrame> createCameraTimeFrameFromFilenames(
339
        FilenameTimeFrameOptions const & options);
340

341
/**
342
     * @brief Create an UncalibratedTimeFrame from image folder filenames
343
     * 
344
     * For when the extracted values don't represent camera frames but some other
345
     * uncalibrated indexing scheme.
346
     * 
347
     * @param options Configuration options (same as legacy version)
348
     * @return A shared pointer to the created UncalibratedTimeFrame, or nullptr on failure
349
     */
350
[[nodiscard]] std::shared_ptr<UncalibratedTimeFrame> createUncalibratedTimeFrameFromFilenames(
351
        FilenameTimeFrameOptions const & options);
352
}// namespace TimeFrameUtils
353

354
#endif// TIMEFRAME_V2_HPP
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