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

paulmthompson / WhiskerToolbox / 15865657612

25 Jun 2025 01:50AM UTC coverage: 69.485% (+0.9%) from 68.538%
15865657612

push

github

paulmthompson
add principal axis data transform

371 of 383 new or added lines in 3 files covered. (96.87%)

139 existing lines in 4 files now uncovered.

10058 of 14475 relevant lines covered (69.49%)

1210.31 hits per line

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

58.26
/src/WhiskerToolbox/DataViewer/DigitalInterval/MVP_DigitalInterval.cpp
1
#include "MVP_DigitalInterval.hpp"
2

3
#include "DigitalIntervalSeriesDisplayOptions.hpp"
4
#include "PlottingManager/PlottingManager.hpp"
5

6
#include <algorithm>
7
#include <cmath>
8
#include <iostream>
9
#include <random>
10

11

12
glm::mat4 new_getIntervalModelMat(NewDigitalIntervalSeriesDisplayOptions const & display_options,
8✔
13
                                  PlottingManager const & plotting_manager) {
14

15
    static_cast<void>(plotting_manager);
16

17
    auto Model = glm::mat4(1.0f);
8✔
18

19
    // Apply global zoom scaling
20
    float const global_scale = display_options.global_zoom * display_options.global_vertical_scale;
8✔
21

22
    // For digital intervals extending full canvas, we want them to span the entire allocated height
23
    // The allocated_height represents the total height available for this series
24
    if (display_options.extend_full_canvas) {
8✔
25
        // Scale to use the full allocated height with margin factor
26
        float const height_scale = display_options.allocated_height * display_options.margin_factor * 0.5f;
8✔
27
        Model = glm::scale(Model, glm::vec3(1.0f, height_scale * global_scale, 1.0f));
8✔
28

29
        // Translate to allocated center position
30
        Model = glm::translate(Model, glm::vec3(0.0f, display_options.allocated_y_center, 0.0f));
8✔
31
    } else {
32
        // Apply standard scaling and positioning
33
        Model = glm::scale(Model, glm::vec3(1.0f, global_scale, 1.0f));
×
34
        Model = glm::translate(Model, glm::vec3(0.0f, display_options.allocated_y_center, 0.0f));
×
35
    }
36

37
    return Model;
8✔
38
}
39

40
glm::mat4 new_getIntervalViewMat(PlottingManager const & plotting_manager) {
8✔
41

42
    static_cast<void>(plotting_manager);
43

44
    auto View = glm::mat4(1.0f);
8✔
45

46
    // Digital intervals should NOT move with vertical panning
47
    // They always extend from the top to bottom of the current view
48
    // Unlike analog series, panning should not affect their position
49
    // (they remain "pinned" to the current viewport)
50

51
    return View;
8✔
52
}
53

54
glm::mat4 new_getIntervalProjectionMat(int start_data_index,
8✔
55
                                       int end_data_index,
56
                                       float y_min,
57
                                       float y_max,
58
                                       PlottingManager const & plotting_manager) {
59

60
    static_cast<void>(y_min);
61
    static_cast<void>(y_max);
62

63
    // Convert data indices to normalized coordinate range
64
    // Map [start_data_index, end_data_index] to [-1, 1] in X
65
    // Map [y_min, y_max] to viewport range in Y
66

67
    auto const left = static_cast<float>(start_data_index);
8✔
68
    auto const right = static_cast<float>(end_data_index);
8✔
69

70
    // Use viewport Y range from plotting manager with any additional pan offset
71
    float const bottom = plotting_manager.viewport_y_min;
8✔
72
    float const top = plotting_manager.viewport_y_max;
8✔
73

74
    // Validate and sanitize input parameters to prevent OpenGL state corruption
75
    float safe_left = left;
8✔
76
    float safe_right = right;
8✔
77
    float safe_bottom = bottom;
8✔
78
    float safe_top = top;
8✔
79
    
80
    // Check for and fix invalid or extreme values that could cause NaN/Infinity
81
    
82
    // 1. Ensure all values are finite
83
    if (!std::isfinite(safe_left)) {
8✔
UNCOV
84
        std::cout << "Warning: Invalid interval left=" << safe_left << ", using fallback" << std::endl;
×
UNCOV
85
        safe_left = 0.0f;
×
86
    }
87
    if (!std::isfinite(safe_right)) {
8✔
UNCOV
88
        std::cout << "Warning: Invalid interval right=" << safe_right << ", using fallback" << std::endl;
×
UNCOV
89
        safe_right = 1000.0f;
×
90
    }
91
    if (!std::isfinite(safe_bottom)) {
8✔
UNCOV
92
        std::cout << "Warning: Invalid interval bottom=" << safe_bottom << ", using fallback" << std::endl;
×
UNCOV
93
        safe_bottom = -1.0f;
×
94
    }
95
    if (!std::isfinite(safe_top)) {
8✔
UNCOV
96
        std::cout << "Warning: Invalid interval top=" << safe_top << ", using fallback" << std::endl;
×
UNCOV
97
        safe_top = 1.0f;
×
98
    }
99
    
100
    // 2. Ensure X range is valid (left < right with minimum separation)
101
    constexpr float min_range = 1e-6f;  // Minimum range to prevent division by zero
8✔
102
    if (safe_right <= safe_left) {
8✔
UNCOV
103
        std::cout << "Warning: Invalid interval X range [" << safe_left << ", " << safe_right 
×
UNCOV
104
                  << "], fixing to valid range" << std::endl;
×
UNCOV
105
        float center = (safe_left + safe_right) * 0.5f;
×
UNCOV
106
        safe_left = center - min_range * 0.5f;
×
UNCOV
107
        safe_right = center + min_range * 0.5f;
×
108
    } else if ((safe_right - safe_left) < min_range) {
8✔
UNCOV
109
        std::cout << "Warning: Interval X range too small [" << safe_left << ", " << safe_right 
×
UNCOV
110
                  << "], expanding to minimum safe range" << std::endl;
×
UNCOV
111
        float center = (safe_left + safe_right) * 0.5f;
×
UNCOV
112
        safe_left = center - min_range * 0.5f;
×
UNCOV
113
        safe_right = center + min_range * 0.5f;
×
114
    }
115
    
116
    // 3. Ensure Y range is valid
117
    if (safe_top <= safe_bottom) {
8✔
118
        std::cout << "Warning: Invalid interval Y range [" << safe_bottom << ", " << safe_top 
×
UNCOV
119
                  << "], fixing to valid range" << std::endl;
×
UNCOV
120
        float center = (safe_bottom + safe_top) * 0.5f;
×
UNCOV
121
        safe_bottom = center - min_range * 0.5f;
×
UNCOV
122
        safe_top = center + min_range * 0.5f;
×
123
    } else if ((safe_top - safe_bottom) < min_range) {
8✔
UNCOV
124
        std::cout << "Warning: Interval Y range too small [" << safe_bottom << ", " << safe_top 
×
UNCOV
125
                  << "], expanding to minimum safe range" << std::endl;
×
UNCOV
126
        float center = (safe_bottom + safe_top) * 0.5f;
×
UNCOV
127
        safe_bottom = center - min_range * 0.5f;
×
128
        safe_top = center + min_range * 0.5f;
×
129
    }
130
    
131
    // 4. Clamp extreme values to prevent numerical issues
132
    constexpr float max_abs_value = 1e8f;  // Large but safe limit
8✔
133
    if (std::abs(safe_left) > max_abs_value || std::abs(safe_right) > max_abs_value) {
8✔
UNCOV
134
        std::cout << "Warning: Extremely large interval X range [" << safe_left << ", " << safe_right 
×
UNCOV
135
                  << "], clamping to safe range" << std::endl;
×
UNCOV
136
        float range = safe_right - safe_left;
×
UNCOV
137
        if (range > 2 * max_abs_value) {
×
UNCOV
138
            safe_left = -max_abs_value;
×
UNCOV
139
            safe_right = max_abs_value;
×
140
        } else {
UNCOV
141
            float center = (safe_left + safe_right) * 0.5f;
×
UNCOV
142
            center = std::clamp(center, -max_abs_value * 0.5f, max_abs_value * 0.5f);
×
UNCOV
143
            safe_left = center - range * 0.5f;
×
UNCOV
144
            safe_right = center + range * 0.5f;
×
145
        }
146
    }
147

148
    // Create orthographic projection matrix with validated parameters
149
    auto Projection = glm::ortho(safe_left, safe_right, safe_bottom, safe_top);
8✔
150
    
151
    // Final validation: check that the resulting matrix is valid
152
    for (int i = 0; i < 4; ++i) {
40✔
153
        for (int j = 0; j < 4; ++j) {
160✔
154
            if (!std::isfinite(Projection[i][j])) {
128✔
UNCOV
155
                std::cout << "Error: Interval projection matrix contains invalid value at [" << i << "][" << j 
×
UNCOV
156
                          << "]=" << Projection[i][j] << ", using identity matrix" << std::endl;
×
UNCOV
157
                return glm::mat4(1.0f);  // Return identity matrix as safe fallback
×
158
            }
159
        }
160
    }
161

162
    return Projection;
8✔
163
}
164

165
std::vector<Interval> generateTestIntervalData(size_t num_intervals,
4✔
166
                                               float max_time,
167
                                               float min_duration,
168
                                               float max_duration,
169
                                               unsigned int seed) {
170
    std::mt19937 gen(seed);
4✔
171
    std::uniform_real_distribution<float> time_dist(0.0f, max_time * 0.8f);// Leave some space for intervals
4✔
172
    std::uniform_real_distribution<float> duration_dist(min_duration, max_duration);
4✔
173

174
    std::vector<Interval> intervals;
4✔
175
    intervals.reserve(num_intervals);
4✔
176

177
    // Generate random intervals
178
    for (size_t i = 0; i < num_intervals; ++i) {
184✔
179
        float start_time = time_dist(gen);
180✔
180
        float duration = duration_dist(gen);
180✔
181
        float end_time = start_time + duration;
180✔
182

183
        // Ensure we don't exceed max_time
184
        if (end_time > max_time) {
180✔
UNCOV
185
            end_time = max_time;
×
UNCOV
186
            start_time = std::max(0.0f, end_time - duration);
×
187
        }
188

189
        // Convert to int64_t for Interval struct
190
        intervals.emplace_back(Interval{static_cast<int64_t>(start_time), static_cast<int64_t>(end_time)});
180✔
191
    }
192

193
    // Sort intervals by start time to ensure ordering requirement
194
    std::sort(intervals.begin(), intervals.end(),
4✔
195
              [](Interval const & a, Interval const & b) {
1,281✔
196
                  return a.start < b.start;
1,281✔
197
              });
198

199
    // Validate all intervals (end > start)
200
    for (auto & interval: intervals) {
184✔
201
        if (interval.end <= interval.start) {
180✔
202
            // This shouldn't happen with our generation method, but just in case
UNCOV
203
            interval.end = interval.start + static_cast<int64_t>(min_duration);
×
204
        }
205
    }
206

207
    return intervals;
4✔
UNCOV
208
}
×
209

210
void setIntervalIntrinsicProperties(std::vector<Interval> const & intervals,
3✔
211
                                    NewDigitalIntervalSeriesDisplayOptions & display_options) {
212
    if (intervals.empty()) {
3✔
UNCOV
213
        return;
×
214
    }
215

216
    // For digital intervals, intrinsic properties are minimal since they're meant to be
217
    // visual indicators rather than quantitative data. The main consideration is
218
    // ensuring good visibility across the canvas.
219

220
    // Calculate some basic statistics for potential future use
221
    int64_t total_duration = 0;
3✔
222
    int64_t min_duration_val = std::numeric_limits<int64_t>::max();
3✔
223
    int64_t max_duration_val = 0;
3✔
224

225
    for (auto const & interval: intervals) {
133✔
226
        int64_t const duration = interval.end - interval.start;
130✔
227
        total_duration += duration;
130✔
228
        min_duration_val = std::min(min_duration_val, duration);
130✔
229
        max_duration_val = std::max(max_duration_val, duration);
130✔
230
    }
231

232
    //int64_t const avg_duration = total_duration / static_cast<int64_t>(intervals.size());
233

234
    // For now, we keep the default display options
235
    // Future enhancements could adjust alpha based on interval density,
236
    // or adjust positioning based on overlap analysis
237

238
    // Ensure alpha is appropriate for overlapping intervals
239
    if (intervals.size() > 50) {
3✔
240
        // With many intervals, reduce alpha to prevent over-saturation
241
        display_options.alpha = std::max(0.1f, 0.3f * std::sqrt(50.0f / static_cast<float>(intervals.size())));
1✔
242
    }
243

244
    // Set full canvas extension for maximum visibility
245
    display_options.extend_full_canvas = true;
3✔
246
}
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