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

paulmthompson / WhiskerToolbox / 17270491352

27 Aug 2025 02:57PM UTC coverage: 65.333%. Remained the same
17270491352

push

github

paulmthompson
Merge branch 'main' of https://github.com/paulmthompson/WhiskerToolbox

352 of 628 new or added lines in 92 files covered. (56.05%)

357 existing lines in 24 files now uncovered.

26429 of 40453 relevant lines covered (65.33%)

1119.34 hits per line

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

67.83
/src/DataManager/transforms/Lines/line_angle.cpp
1
#include "line_angle.hpp"
2

3
#include "AnalogTimeSeries/Analog_Time_Series.hpp"
4
#include "Lines/Line_Data.hpp"
5
#include "CoreGeometry/line_geometry.hpp"
6
#include "utils/polynomial/polynomial_fit.hpp"
7

8
#include <armadillo>
9

10
#include <cmath>
11
#include <map>
12
#include <numbers>
13
#include <vector>
14

15

16
float normalize_angle(float raw_angle, float reference_x, float reference_y) {
27✔
17
    // Calculate the angle of the reference vector (if not the default x-axis)
18
    float reference_angle = 0.0f;
27✔
19
    if (!(reference_x == 1.0f && reference_y == 0.0f)) {
27✔
20
        reference_angle = std::atan2(reference_y, reference_x);
5✔
21
        // Convert to degrees
22
        reference_angle *= 180.0f / static_cast<float>(std::numbers::pi);
5✔
23
    }
24

25
    // Adjust the raw angle by subtracting the reference angle
26
    float normalized_angle = raw_angle - reference_angle;
27✔
27

28
    // Normalize to range [-180, 180]
29
    while (normalized_angle > 180.0f) normalized_angle -= 360.0f;
27✔
30
    while (normalized_angle <= -180.0f) normalized_angle += 360.0f;
27✔
31

32
    return normalized_angle;
27✔
33
}
34

35
// Calculate angle using direct point comparison
36
float calculate_direct_angle(Line2D const & line, float position, float reference_x, float reference_y) {
19✔
37
    if (line.size() < 2) {
19✔
38
        return 0.0f;
×
39
    }
40

41
    // Calculate the index of the position point
42
    auto idx = static_cast<size_t>(position * static_cast<float>((line.size() - 1)));
19✔
43
    if (idx == 0) {
19✔
44
        // If position is at the start, use the first two points
45
        idx = 1;
5✔
46
    } else if (idx >= line.size() - 1) {
14✔
47
        // If position is at the end, use the last two points
48
        idx = line.size() - 2;
1✔
49
    }
50
    if (idx >= line.size()) {
19✔
51
        idx = line.size() - 1;
×
52
    }
53

54
    Point2D<float> const base = line[0];
19✔
55
    Point2D<float> const pos = line[idx];
19✔
56

57
    // Calculate angle in radians (atan2 returns value in range [-π, π])
58
    float raw_angle = std::atan2(pos.y - base.y, pos.x - base.x);
19✔
59

60
    // Convert to degrees
61
    float angle_degrees = raw_angle * 180.0f / static_cast<float>(std::numbers::pi);
19✔
62

63
    // Normalize with respect to the reference vector
64
    return normalize_angle(angle_degrees, reference_x, reference_y);
19✔
65
}
66

67
// Calculate angle using polynomial parameterization
68
float calculate_polynomial_angle(Line2D const & line,
9✔
69
                                 float position,
70
                                 int polynomial_order,
71
                                 float reference_x,
72
                                 float reference_y) {
73
    if (line.size() < static_cast<size_t>(polynomial_order + 1)) {
9✔
74
        // Fall back to direct method if not enough points
75
        return calculate_direct_angle(line, position, reference_x, reference_y);
1✔
76
    }
77

78
    // Extract x and y coordinates
79
    std::vector<double> x_coords;
8✔
80
    std::vector<double> y_coords;
8✔
81

82
    auto length = calc_length(line);
8✔
83

84
    x_coords.reserve(line.size());
8✔
85
    y_coords.reserve(line.size());
8✔
86
    auto t_values_f = calc_cumulative_length_vector(line);
8✔
87
    for (size_t i = 0; i < line.size(); ++i) {
1,053✔
88
        // Normalize t_values to [0, 1]
89
        t_values_f[i] /= length;
1,045✔
90
    }
91

92
    std::vector<double> t_values(t_values_f.begin(), t_values_f.end());
24✔
93

94
    for (size_t i = 0; i < line.size(); ++i) {
1,053✔
95
        x_coords.push_back(static_cast<double>(line[i].x));
1,045✔
96
        y_coords.push_back(static_cast<double>(line[i].y));
1,045✔
97
    }
98

99
    // Fit polynomials to x(t) and y(t)
100
    std::vector<double> x_coeffs = fit_polynomial(t_values, x_coords, polynomial_order);
8✔
101
    std::vector<double> y_coeffs = fit_polynomial(t_values, y_coords, polynomial_order);
8✔
102

103
    if (x_coeffs.empty() || y_coeffs.empty()) {
8✔
104
        // Fall back to direct method if fitting failed
105
        return calculate_direct_angle(line, position, reference_x, reference_y);
×
106
    }
107

108
    // Evaluate derivatives at the specified position
109
    double t = static_cast<double>(position);
8✔
110
    double dx_dt = evaluate_polynomial_derivative(x_coeffs, t);
8✔
111
    double dy_dt = evaluate_polynomial_derivative(y_coeffs, t);
8✔
112

113
    // Calculate angle using the derivatives
114
    double raw_angle = std::atan2(dy_dt, dx_dt);
8✔
115

116
    // Convert to degrees
117
    float angle_degrees = static_cast<float>(raw_angle * 180.0 / std::numbers::pi);
8✔
118

119
    // Normalize with respect to the reference vector
120
    return normalize_angle(angle_degrees, reference_x, reference_y);
8✔
121
}
8✔
122

123
///////////////////////////////////////////////////////////////////////////////
124

125
std::shared_ptr<AnalogTimeSeries> line_angle(LineData const * line_data, LineAngleParameters const * params) {
27✔
126
    // Call the version with progress reporting but ignore progress
127
    return line_angle(line_data, params, [](int) {});
27✔
128
}
129

130
std::shared_ptr<AnalogTimeSeries> line_angle(LineData const * line_data,
27✔
131
                                             LineAngleParameters const * params,
132
                                             ProgressCallback progressCallback) {
133

134
    static_cast<void>(progressCallback);
135

136
    std::map<int, float> angles;
27✔
137

138
    // Use default parameters if none provided
139
    float position = params ? params->position : 0.2f;
27✔
140
    AngleCalculationMethod method = params ? params->method : AngleCalculationMethod::DirectPoints;
27✔
141
    int polynomial_order = params ? params->polynomial_order : 3;
27✔
142
    float reference_x = params ? params->reference_x : 1.0f;
27✔
143
    float reference_y = params ? params->reference_y : 0.0f;
27✔
144

145
    // Ensure position is within valid range
146
    position = std::max(0.0f, std::min(position, 1.0f));
27✔
147

148
    // Normalize reference vector if needed
149
    if (reference_x != 0.0f || reference_y != 0.0f) {
27✔
150
        float length = std::sqrt(reference_x * reference_x + reference_y * reference_y);
26✔
151
        if (length > 0.0f) {
26✔
152
            reference_x /= length;
26✔
153
            reference_y /= length;
26✔
154
        } else {
155
            // Default to x-axis if invalid reference
156
            reference_x = 1.0f;
×
157
            reference_y = 0.0f;
×
158
        }
159
    } else {
26✔
160
        // Default to x-axis if zero reference
161
        reference_x = 1.0f;
1✔
162
        reference_y = 0.0f;
1✔
163
    }
164

165
    for (auto const & line_and_time: line_data->GetAllLinesAsRange()) {
55✔
166
        if (line_and_time.lines.empty()) {
28✔
167
            continue;
×
168
        }
169

170
        Line2D const & line = line_and_time.lines[0];
28✔
171

172
        if (line.size() < 2) {
28✔
173
            continue;
1✔
174
        }
175

176
        float angle = 0.0f;
27✔
177

178
        // Calculate angle using the selected method
179
        if (method == AngleCalculationMethod::DirectPoints) {
27✔
180
            angle = calculate_direct_angle(line, position, reference_x, reference_y);
18✔
181
        } else if (method == AngleCalculationMethod::PolynomialFit) {
9✔
182
            angle = calculate_polynomial_angle(line, position, polynomial_order, reference_x, reference_y);
9✔
183
        }
184

185
        angles[static_cast<int>(line_and_time.time.getValue())] = angle;
27✔
186
    }
187

188
    return std::make_shared<AnalogTimeSeries>(angles);
54✔
189
}
27✔
190

191
///////////////////////////////////////////////////////////////////////////////
192

193
std::string LineAngleOperation::getName() const {
×
194
    return "Calculate Line Angle";
×
195
}
196

197
std::type_index LineAngleOperation::getTargetInputTypeIndex() const {
×
198
    return typeid(std::shared_ptr<LineData>);
×
199
}
200

201
bool LineAngleOperation::canApply(DataTypeVariant const & dataVariant) const {
×
202
    if (!std::holds_alternative<std::shared_ptr<LineData>>(dataVariant)) {
×
203
        return false;
×
204
    }
205

206
    auto const * ptr_ptr = std::get_if<std::shared_ptr<LineData>>(&dataVariant);
×
207

208
    return ptr_ptr && *ptr_ptr;
×
209
}
210

211
std::unique_ptr<TransformParametersBase> LineAngleOperation::getDefaultParameters() const {
×
212
    return std::make_unique<LineAngleParameters>();
×
213
}
214

215
DataTypeVariant LineAngleOperation::execute(DataTypeVariant const & dataVariant,
×
216
                                            TransformParametersBase const * transformParameters,
217
                                            ProgressCallback progressCallback) {
UNCOV
218
    auto const * ptr_ptr = std::get_if<std::shared_ptr<LineData>>(&dataVariant);
×
219

220
    if (!ptr_ptr || !(*ptr_ptr)) {
×
221
        std::cerr << "LineAngleOperation::execute called with incompatible variant type or null data." << std::endl;
×
222
        return {};// Return empty variant
×
223
    }
224

225
    LineData const * line_raw_ptr = (*ptr_ptr).get();
×
226

227
    LineAngleParameters const * typed_params = nullptr;
×
228
    if (transformParameters) {
×
229
        typed_params = dynamic_cast<LineAngleParameters const *>(transformParameters);
×
230
        if (!typed_params) {
×
231
            std::cerr << "LineAngleOperation::execute: Invalid parameter type" << std::endl;
×
232
        }
233
    }
234

NEW
235
    std::shared_ptr<AnalogTimeSeries> result_ts = line_angle(line_raw_ptr, typed_params, progressCallback);
×
236

237
    // Handle potential failure from the calculation function
238
    if (!result_ts) {
×
239
        std::cerr << "LineAngleOperation::execute: 'line_angle' failed to produce a result." << std::endl;
×
240
        return {};// Return empty variant
×
241
    }
242

243
    std::cout << "LineAngleOperation executed successfully using variant input." << std::endl;
×
244
    return result_ts;
×
245
}
×
246

NEW
247
DataTypeVariant LineAngleOperation::execute(DataTypeVariant const & dataVariant,
×
248
                                            TransformParametersBase const * transformParameters) {
249

NEW
250
    return execute(dataVariant, transformParameters, [](int){});
×
251
}
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