• 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

59.81
/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,
14✔
11
                               PlottingManager const & plotting_manager) {
12
    glm::mat4 Model(1.0f);
14✔
13

14
    if (display_options.plotting_mode == EventPlottingMode::FullCanvas) {
14✔
15
        // Full Canvas Mode: Events extend from top to bottom of entire plot (like digital intervals)
16
        // Events are rendered as lines spanning the full viewport height
17
        // Scale to full viewport height with margin factor
18
        float const height_scale = (plotting_manager.viewport_y_max - plotting_manager.viewport_y_min) *
4✔
19
                                   display_options.margin_factor;
4✔
20

21
        // Center events in the middle of the viewport
22
        float const center_y = (plotting_manager.viewport_y_max + plotting_manager.viewport_y_min) * 0.5f;
4✔
23

24
        // Apply scaling for full canvas height
25
        Model[1][1] = height_scale * 0.5f;// Half scale because we'll map [-1,1] to full height
4✔
26

27
        // Apply translation to center
28
        Model[3][1] = center_y;
4✔
29

30
    } else if (display_options.plotting_mode == EventPlottingMode::Stacked) {
10✔
31
        // Stacked Mode: Events are positioned within allocated space (like analog series)
32
        // Events extend within their allocated height portion
33

34
        // Scale to allocated height with margin factor
35
        float const height_scale = display_options.allocated_height * display_options.margin_factor;
10✔
36

37
        // Apply scaling for allocated height
38
        Model[1][1] = height_scale * 0.5f;// Half scale because we'll map [-1,1] to allocated height
10✔
39

40
        // Apply translation to allocated center
41
        Model[3][1] = display_options.allocated_y_center;
10✔
42
    }
43

44
    // Apply global scaling factors
45
    Model[1][1] *= display_options.global_vertical_scale * plotting_manager.global_vertical_scale;
14✔
46

47
    return Model;
14✔
48
}
49

50
glm::mat4 new_getEventViewMat(NewDigitalEventSeriesDisplayOptions const & display_options,
21✔
51
                              PlottingManager const & plotting_manager) {
52
    auto View = glm::mat4(1.0f);
21✔
53

54
    // Panning behavior depends on plotting mode
55
    if (display_options.plotting_mode == EventPlottingMode::FullCanvas) {
21✔
56
        // Full Canvas Mode: Events stay viewport-pinned (like digital intervals)
57
        // No panning applied - events remain fixed to viewport bounds
58

59
    } else if (display_options.plotting_mode == EventPlottingMode::Stacked) {
15✔
60
        // Stacked Mode: Events move with content (like analog series)
61
        // Apply global vertical panning
62
        if (plotting_manager.vertical_pan_offset != 0.0f) {
15✔
63
            View = glm::translate(View, glm::vec3(0.0f, plotting_manager.vertical_pan_offset, 0.0f));
10✔
64
        }
65
    }
66

67
    return View;
21✔
68
}
69

70
glm::mat4 new_getEventProjectionMat(int start_data_index,
2✔
71
                                    int end_data_index,
72
                                    float y_min,
73
                                    float y_max,
74
                                    PlottingManager const & plotting_manager) {
75

76
    static_cast<void>(plotting_manager);
77

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

85
    auto const data_start = static_cast<float>(start_data_index);
2✔
86
    auto const data_end = static_cast<float>(end_data_index);
2✔
87

88
    // Validate and sanitize input parameters to prevent OpenGL state corruption
89
    float safe_data_start = data_start;
2✔
90
    float safe_data_end = data_end;
2✔
91
    float safe_y_min = y_min;
2✔
92
    float safe_y_max = y_max;
2✔
93
    
94
    // Check for and fix invalid or extreme values that could cause NaN/Infinity
95
    
96
    // 1. Ensure all values are finite
97
    if (!std::isfinite(safe_data_start)) {
2✔
UNCOV
98
        std::cout << "Warning: Invalid event data_start=" << safe_data_start << ", using fallback" << std::endl;
×
UNCOV
99
        safe_data_start = 0.0f;
×
100
    }
101
    if (!std::isfinite(safe_data_end)) {
2✔
UNCOV
102
        std::cout << "Warning: Invalid event data_end=" << safe_data_end << ", using fallback" << std::endl;
×
UNCOV
103
        safe_data_end = 1000.0f;
×
104
    }
105
    if (!std::isfinite(safe_y_min)) {
2✔
UNCOV
106
        std::cout << "Warning: Invalid event y_min=" << safe_y_min << ", using fallback" << std::endl;
×
UNCOV
107
        safe_y_min = -1.0f;
×
108
    }
109
    if (!std::isfinite(safe_y_max)) {
2✔
UNCOV
110
        std::cout << "Warning: Invalid event y_max=" << safe_y_max << ", using fallback" << std::endl;
×
UNCOV
111
        safe_y_max = 1.0f;
×
112
    }
113
    
114
    // 2. Ensure data range is valid (start < end with minimum separation)
115
    constexpr float min_range = 1e-6f;  // Minimum range to prevent division by zero
2✔
116
    if (safe_data_end <= safe_data_start) {
2✔
117
        std::cout << "Warning: Invalid event data range [" << safe_data_start << ", " << safe_data_end 
×
UNCOV
118
                  << "], fixing to valid range" << std::endl;
×
UNCOV
119
        float center = (safe_data_start + safe_data_end) * 0.5f;
×
UNCOV
120
        safe_data_start = center - min_range * 0.5f;
×
UNCOV
121
        safe_data_end = center + min_range * 0.5f;
×
122
    } else if ((safe_data_end - safe_data_start) < min_range) {
2✔
UNCOV
123
        std::cout << "Warning: Event data range too small [" << safe_data_start << ", " << safe_data_end 
×
UNCOV
124
                  << "], expanding to minimum safe range" << std::endl;
×
UNCOV
125
        float center = (safe_data_start + safe_data_end) * 0.5f;
×
UNCOV
126
        safe_data_start = center - min_range * 0.5f;
×
UNCOV
127
        safe_data_end = center + min_range * 0.5f;
×
128
    }
129
    
130
    // 3. Ensure Y range is valid
131
    if (safe_y_max <= safe_y_min) {
2✔
132
        std::cout << "Warning: Invalid event Y range [" << safe_y_min << ", " << safe_y_max 
×
UNCOV
133
                  << "], fixing to valid range" << std::endl;
×
UNCOV
134
        float center = (safe_y_min + safe_y_max) * 0.5f;
×
UNCOV
135
        safe_y_min = center - min_range * 0.5f;
×
UNCOV
136
        safe_y_max = center + min_range * 0.5f;
×
137
    } else if ((safe_y_max - safe_y_min) < min_range) {
2✔
UNCOV
138
        std::cout << "Warning: Event Y range too small [" << safe_y_min << ", " << safe_y_max 
×
UNCOV
139
                  << "], expanding to minimum safe range" << std::endl;
×
UNCOV
140
        float center = (safe_y_min + safe_y_max) * 0.5f;
×
UNCOV
141
        safe_y_min = center - min_range * 0.5f;
×
UNCOV
142
        safe_y_max = center + min_range * 0.5f;
×
143
    }
144
    
145
    // 4. Clamp extreme values to prevent numerical issues
146
    constexpr float max_abs_value = 1e8f;  // Large but safe limit
2✔
147
    if (std::abs(safe_data_start) > max_abs_value || std::abs(safe_data_end) > max_abs_value) {
2✔
UNCOV
148
        std::cout << "Warning: Extremely large event data range [" << safe_data_start << ", " << safe_data_end 
×
UNCOV
149
                  << "], clamping to safe range" << std::endl;
×
UNCOV
150
        float range = safe_data_end - safe_data_start;
×
UNCOV
151
        if (range > 2 * max_abs_value) {
×
UNCOV
152
            safe_data_start = -max_abs_value;
×
UNCOV
153
            safe_data_end = max_abs_value;
×
154
        } else {
UNCOV
155
            float center = (safe_data_start + safe_data_end) * 0.5f;
×
UNCOV
156
            center = std::clamp(center, -max_abs_value * 0.5f, max_abs_value * 0.5f);
×
UNCOV
157
            safe_data_start = center - range * 0.5f;
×
UNCOV
158
            safe_data_end = center + range * 0.5f;
×
159
        }
160
    }
161

162
    // Create orthographic projection matrix with validated parameters
163
    // This maps world coordinates to normalized device coordinates [-1, 1]
164
    auto Projection = glm::ortho(safe_data_start, safe_data_end, safe_y_min, safe_y_max);
2✔
165
    
166
    // Final validation: check that the resulting matrix is valid
167
    for (int i = 0; i < 4; ++i) {
10✔
168
        for (int j = 0; j < 4; ++j) {
40✔
169
            if (!std::isfinite(Projection[i][j])) {
32✔
UNCOV
170
                std::cout << "Error: Event projection matrix contains invalid value at [" << i << "][" << j 
×
UNCOV
171
                          << "]=" << Projection[i][j] << ", using identity matrix" << std::endl;
×
UNCOV
172
                return glm::mat4(1.0f);  // Return identity matrix as safe fallback
×
173
            }
174
        }
175
    }
176

177
    return Projection;
2✔
178
}
179

180
// Helper functions for test data generation and intrinsic properties
181

182
std::vector<EventData> generateTestEventData(size_t num_events,
7✔
183
                                             float max_time,
184
                                             unsigned int seed) {
185
    std::mt19937 gen(seed);
7✔
186
    std::uniform_real_distribution<float> time_dist(0.0f, max_time);
7✔
187

188
    std::vector<EventData> events;
7✔
189
    events.reserve(num_events);
7✔
190

191
    for (size_t i = 0; i < num_events; ++i) {
1,007✔
192
        float event_time = time_dist(gen);
1,000✔
193
        events.emplace_back(event_time);
1,000✔
194
    }
195

196
    // Sort events by time for optimal visualization
197
    std::sort(events.begin(), events.end(),
7✔
198
              [](EventData const & a, EventData const & b) {
9,680✔
199
                  return a.time < b.time;
9,680✔
200
              });
201

202
    return events;
14✔
UNCOV
203
}
×
204

205
void setEventIntrinsicProperties(std::vector<EventData> const & events,
7✔
206
                                 NewDigitalEventSeriesDisplayOptions & display_options) {
207
    if (events.empty()) {
7✔
208
        return;
1✔
209
    }
210

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

214
    // Adjust alpha based on event count to prevent over-saturation
215
    if (events.size() > 100) {
6✔
216
        display_options.alpha = std::max(0.3f, display_options.alpha * 0.7f);
3✔
217
    } else if (events.size() > 50) {
3✔
UNCOV
218
        display_options.alpha = std::max(0.5f, display_options.alpha * 0.85f);
×
219
    }
220

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