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

paulmthompson / WhiskerToolbox / 17810127916

17 Sep 2025 08:40PM UTC coverage: 71.967% (+0.3%) from 71.661%
17810127916

push

github

paulmthompson
fixed error in plotting digital events

283 of 300 new or added lines in 5 files covered. (94.33%)

314 existing lines in 3 files now uncovered.

39620 of 55053 relevant lines covered (71.97%)

1301.32 hits per line

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

60.19
/src/WhiskerToolbox/DataViewer/DigitalEvent/MVP_DigitalEvent.cpp
1
#include "MVP_DigitalEvent.hpp"
2

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

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

10
glm::mat4 new_getEventModelMat(NewDigitalEventSeriesDisplayOptions const & display_options,
45✔
11
                               PlottingManager const & plotting_manager) {
12
    glm::mat4 Model(1.0f);
45✔
13

14
    if (display_options.plotting_mode == EventPlottingMode::FullCanvas) {
45✔
15
        // Full Canvas Mode: extend full viewport height, centered (ignore allocated_* from stacked path)
16
        float const height_scale = (plotting_manager.viewport_y_max - plotting_manager.viewport_y_min) * display_options.margin_factor;
5✔
17
        float const center_y = (plotting_manager.viewport_y_max + plotting_manager.viewport_y_min) * 0.5f;
5✔
18
        Model[1][1] = height_scale * 0.5f;// map [-1,1] -> full height with margin
5✔
19
        Model[3][1] = center_y;
5✔
20

21
    } else if (display_options.plotting_mode == EventPlottingMode::Stacked) {
40✔
22
        // Stacked Mode: Events are positioned within allocated space (like analog series)
23
        // Events extend within their allocated height portion
24

25
        // Prefer explicit event height if provided, but never exceed allocated lane
26
        float const desired_height = (display_options.event_height > 0.0f)
40✔
27
                                     ? display_options.event_height
40✔
28
                                     : display_options.allocated_height;
29
        float const height_scale = std::min(desired_height, display_options.allocated_height) * display_options.margin_factor;
40✔
30

31
        // Apply scaling for allocated height
32
        Model[1][1] = height_scale * 0.5f;// Half scale because we'll map [-1,1] to allocated height
40✔
33

34
        // Apply translation to allocated center
35
        Model[3][1] = display_options.allocated_y_center;
40✔
36
    }
37

38
    // Apply global scaling factors
39
    //Model[1][1] *= display_options.global_vertical_scale * plotting_manager.global_vertical_scale;
40
    Model[1][1] *= display_options.global_vertical_scale;
45✔
41

42
    return Model;
45✔
43
}
44

45
glm::mat4 new_getEventViewMat(NewDigitalEventSeriesDisplayOptions const & display_options,
52✔
46
                              PlottingManager const & plotting_manager) {
47
    auto View = glm::mat4(1.0f);
52✔
48

49
    // Panning behavior depends on plotting mode
50
    if (display_options.plotting_mode == EventPlottingMode::FullCanvas) {
52✔
51
        // Full Canvas Mode: Events stay viewport-pinned (like digital intervals)
52
        // No panning applied - events remain fixed to viewport bounds
53

54
    } else if (display_options.plotting_mode == EventPlottingMode::Stacked) {
45✔
55
        // Stacked Mode: Events move with content (like analog series)
56
        // Apply global vertical panning
57
        if (plotting_manager.vertical_pan_offset != 0.0f) {
45✔
58
            View = glm::translate(View, glm::vec3(0.0f, plotting_manager.vertical_pan_offset, 0.0f));
10✔
59
        }
60
    }
61

62
    return View;
52✔
63
}
64

65
glm::mat4 new_getEventProjectionMat(int start_data_index,
33✔
66
                                    int end_data_index,
67
                                    float y_min,
68
                                    float y_max,
69
                                    PlottingManager const & plotting_manager) {
70

71
    static_cast<void>(plotting_manager);
72

73
    // Map data indices to normalized device coordinates [-1, 1]
74
    // X-axis: map [start_data_index, end_data_index] to screen width
75
    // Y-axis: map [y_min, y_max] to viewport height
76
    //
77
    // Note: Events use the same projection logic regardless of plotting mode
78
    // The mode-dependent behavior is handled in Model and View matrices
79

80
    auto const data_start = static_cast<float>(start_data_index);
33✔
81
    auto const data_end = static_cast<float>(end_data_index);
33✔
82

83
    // Validate and sanitize input parameters to prevent OpenGL state corruption
84
    float safe_data_start = data_start;
33✔
85
    float safe_data_end = data_end;
33✔
86
    float safe_y_min = y_min;
33✔
87
    float safe_y_max = y_max;
33✔
88

89
    // Check for and fix invalid or extreme values that could cause NaN/Infinity
90

91
    // 1. Ensure all values are finite
92
    if (!std::isfinite(safe_data_start)) {
33✔
93
        std::cout << "Warning: Invalid event data_start=" << safe_data_start << ", using fallback" << std::endl;
×
94
        safe_data_start = 0.0f;
×
95
    }
96
    if (!std::isfinite(safe_data_end)) {
33✔
97
        std::cout << "Warning: Invalid event data_end=" << safe_data_end << ", using fallback" << std::endl;
×
98
        safe_data_end = 1000.0f;
×
99
    }
100
    if (!std::isfinite(safe_y_min)) {
33✔
101
        std::cout << "Warning: Invalid event y_min=" << safe_y_min << ", using fallback" << std::endl;
×
102
        safe_y_min = -1.0f;
×
103
    }
104
    if (!std::isfinite(safe_y_max)) {
33✔
105
        std::cout << "Warning: Invalid event y_max=" << safe_y_max << ", using fallback" << std::endl;
×
106
        safe_y_max = 1.0f;
×
107
    }
108

109
    // 2. Ensure data range is valid (start < end with minimum separation)
110
    constexpr float min_range = 1e-6f;// Minimum range to prevent division by zero
33✔
111
    if (safe_data_end <= safe_data_start) {
33✔
NEW
112
        std::cout << "Warning: Invalid event data range [" << safe_data_start << ", " << safe_data_end
×
113
                  << "], fixing to valid range" << std::endl;
×
114
        float center = (safe_data_start + safe_data_end) * 0.5f;
×
115
        safe_data_start = center - min_range * 0.5f;
×
116
        safe_data_end = center + min_range * 0.5f;
×
117
    } else if ((safe_data_end - safe_data_start) < min_range) {
33✔
NEW
118
        std::cout << "Warning: Event data range too small [" << safe_data_start << ", " << safe_data_end
×
119
                  << "], expanding to minimum safe range" << std::endl;
×
120
        float center = (safe_data_start + safe_data_end) * 0.5f;
×
121
        safe_data_start = center - min_range * 0.5f;
×
122
        safe_data_end = center + min_range * 0.5f;
×
123
    }
124

125
    // 3. Ensure Y range is valid
126
    if (safe_y_max <= safe_y_min) {
33✔
NEW
127
        std::cout << "Warning: Invalid event Y range [" << safe_y_min << ", " << safe_y_max
×
128
                  << "], fixing to valid range" << std::endl;
×
129
        float center = (safe_y_min + safe_y_max) * 0.5f;
×
130
        safe_y_min = center - min_range * 0.5f;
×
131
        safe_y_max = center + min_range * 0.5f;
×
132
    } else if ((safe_y_max - safe_y_min) < min_range) {
33✔
NEW
133
        std::cout << "Warning: Event Y range too small [" << safe_y_min << ", " << safe_y_max
×
134
                  << "], expanding to minimum safe range" << std::endl;
×
135
        float center = (safe_y_min + safe_y_max) * 0.5f;
×
136
        safe_y_min = center - min_range * 0.5f;
×
137
        safe_y_max = center + min_range * 0.5f;
×
138
    }
139

140
    // 4. Clamp extreme values to prevent numerical issues
141
    constexpr float max_abs_value = 1e8f;// Large but safe limit
33✔
142
    if (std::abs(safe_data_start) > max_abs_value || std::abs(safe_data_end) > max_abs_value) {
33✔
NEW
143
        std::cout << "Warning: Extremely large event data range [" << safe_data_start << ", " << safe_data_end
×
144
                  << "], clamping to safe range" << std::endl;
×
145
        float range = safe_data_end - safe_data_start;
×
146
        if (range > 2 * max_abs_value) {
×
147
            safe_data_start = -max_abs_value;
×
148
            safe_data_end = max_abs_value;
×
149
        } else {
150
            float center = (safe_data_start + safe_data_end) * 0.5f;
×
151
            center = std::clamp(center, -max_abs_value * 0.5f, max_abs_value * 0.5f);
×
152
            safe_data_start = center - range * 0.5f;
×
153
            safe_data_end = center + range * 0.5f;
×
154
        }
155
    }
156

157
    // Create orthographic projection matrix with validated parameters
158
    // This maps world coordinates to normalized device coordinates [-1, 1]
159
    auto Projection = glm::ortho(safe_data_start, safe_data_end, safe_y_min, safe_y_max);
33✔
160

161
    // Final validation: check that the resulting matrix is valid
162
    for (int i = 0; i < 4; ++i) {
165✔
163
        for (int j = 0; j < 4; ++j) {
660✔
164
            if (!std::isfinite(Projection[i][j])) {
528✔
NEW
165
                std::cout << "Error: Event projection matrix contains invalid value at [" << i << "][" << j
×
166
                          << "]=" << Projection[i][j] << ", using identity matrix" << std::endl;
×
NEW
167
                return glm::mat4(1.0f);// Return identity matrix as safe fallback
×
168
            }
169
        }
170
    }
171

172
    return Projection;
33✔
173
}
174

175
// Helper functions for test data generation and intrinsic properties
176

177
std::vector<EventData> generateTestEventData(size_t num_events,
7✔
178
                                             float max_time,
179
                                             unsigned int seed) {
180
    std::mt19937 gen(seed);
7✔
181
    std::uniform_real_distribution<float> time_dist(0.0f, max_time);
7✔
182

183
    std::vector<EventData> events;
7✔
184
    events.reserve(num_events);
7✔
185

186
    for (size_t i = 0; i < num_events; ++i) {
1,007✔
187
        float event_time = time_dist(gen);
1,000✔
188
        events.emplace_back(event_time);
1,000✔
189
    }
190

191
    // Sort events by time for optimal visualization
192
    std::sort(events.begin(), events.end(),
7✔
193
              [](EventData const & a, EventData const & b) {
9,680✔
194
                  return a.time < b.time;
9,680✔
195
              });
196

197
    return events;
14✔
198
}
×
199

200
void setEventIntrinsicProperties(std::vector<EventData> const & events,
7✔
201
                                 NewDigitalEventSeriesDisplayOptions & display_options) {
202
    if (events.empty()) {
7✔
203
        return;
1✔
204
    }
205

206
    // For events, we don't need complex intrinsic properties like analog series
207
    // Events are simple vertical lines, so basic configuration is sufficient
208

209
    // Adjust alpha based on event count to prevent over-saturation
210
    if (events.size() > 100) {
6✔
211
        display_options.alpha = std::max(0.3f, display_options.alpha * 0.7f);
3✔
212
    } else if (events.size() > 50) {
3✔
213
        display_options.alpha = std::max(0.5f, display_options.alpha * 0.85f);
×
214
    }
215

216
    // For dense event series, reduce line thickness to avoid clutter
217
    if (events.size() > 200) {
6✔
218
        display_options.line_thickness = std::max(1, display_options.line_thickness - 1);
2✔
219
    }
220
}
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