• 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

0.0
/src/DataManager/transforms/Masks/mask_to_line.cpp
1
#include "mask_to_line.hpp"
2

3
#include "Lines/Line_Data.hpp"
4
#include "Masks/Mask_Data.hpp"
5

6
#include "CoreGeometry/line_resampling.hpp"
7
#include "CoreGeometry/order_line.hpp"
8
#include "Masks/utils/skeletonize.hpp"
9
#include "utils/polynomial/parametric_polynomial_utils.hpp"
10
#include "utils/polynomial/polynomial_fit.hpp"
11

12
#include <armadillo>
13

14
#include <chrono>
15
#include <cmath>
16
#include <iostream>
17
#include <numeric>// for std::accumulate
18
#include <vector>
19

20

21
// Helper function to fit a polynomial to the given data
22
std::vector<double> fit_polynomial_to_points(Line2D const & points, int order) {
×
23
    if (points.size() <= static_cast<size_t>(order)) {
×
24
        return {};// Not enough data points
×
25
    }
26

27
    // Calculate t values using the helper function
28
    std::vector<double> t_values = compute_t_values(points);
×
29
    if (t_values.empty()) {
×
30
        return {};
×
31
    }
32

33
    // Extract x and y coordinates
34
    std::vector<double> x_coords;
×
35
    std::vector<double> y_coords;
×
36

37
    x_coords.reserve(points.size());
×
38
    y_coords.reserve(points.size());
×
39

40
    for (auto const & point: points) {
×
41
        x_coords.push_back(static_cast<double>(point.x));
×
42
        y_coords.push_back(static_cast<double>(point.y));
×
43
    }
44

45
    // Create Armadillo matrix for Vandermonde matrix
NEW
46
    arma::mat X(t_values.size(), static_cast<arma::uword>(order) + 1);
×
47
    arma::vec Y(y_coords.data(), y_coords.size());
×
48

49
    // Build Vandermonde matrix
50
    for (size_t i = 0; i < t_values.size(); ++i) {
×
51
        for (int j = 0; j <= order; ++j) {
×
NEW
52
            X(i, static_cast<arma::uword>(j)) = std::pow(t_values[i], static_cast<double>(j));
×
53
        }
54
    }
55

56
    // Solve least squares problem
57
    arma::vec coeffs;
×
58
    bool success = arma::solve(coeffs, X, Y);
×
59

60
    if (!success) {
×
61
        return {};// Failed to solve
×
62
    }
63

64
    // Convert Armadillo vector to std::vector
65
    std::vector<double> result(coeffs.begin(), coeffs.end());
×
66
    return result;
×
67
}
×
68

69
ParametricCoefficients fit_parametric_polynomials(Line2D const & points, int order) {
×
70
    if (points.size() <= static_cast<size_t>(order)) {
×
NEW
71
        return {{}, {}, false};
×
72
    }
73

74
    std::vector<double> t_values = compute_t_values(points);
×
75
    if (t_values.empty()) {
×
76
        return {{}, {}, false};
×
77
    }
78

79
    std::vector<double> x_coords;
×
80
    std::vector<double> y_coords;
×
81
    x_coords.reserve(points.size());
×
82
    y_coords.reserve(points.size());
×
83

84
    for (auto const & point: points) {
×
85
        x_coords.push_back(static_cast<double>(point.x));
×
86
        y_coords.push_back(static_cast<double>(point.y));
×
87
    }
88

NEW
89
    std::vector<double> x_coeffs_fit = fit_single_dimension_polynomial_internal(x_coords, t_values, order);
×
NEW
90
    std::vector<double> y_coeffs_fit = fit_single_dimension_polynomial_internal(y_coords, t_values, order);
×
91

NEW
92
    if (x_coeffs_fit.empty() || y_coeffs_fit.empty()) {
×
93
        return {{}, {}, false};
×
94
    }
95

NEW
96
    return {x_coeffs_fit, y_coeffs_fit, true};
×
97
}
×
98

99
// Helper function to generate a smoothed line from parametric polynomial coefficients
100
Line2D generate_smoothed_line(
×
101
        Line2D const & original_points,// Used to estimate total length
102
        std::vector<double> const & x_coeffs,
103
        std::vector<double> const & y_coeffs,
104
        int order,
105
        float target_spacing) {
106

107
    static_cast<void>(order);
108

109
    if (original_points.empty() || x_coeffs.empty() || y_coeffs.empty()) {
×
110
        return {};
×
111
    }
112

113
    // Estimate total arc length from original points for determining number of samples
NEW
114
    float total_length = 0.0f;
×
115
    if (original_points.size() > 1) {
×
116
        for (size_t i = 1; i < original_points.size(); ++i) {
×
NEW
117
            float dx = original_points[i].x - original_points[i - 1].x;
×
NEW
118
            float dy = original_points[i].y - original_points[i - 1].y;
×
UNCOV
119
            total_length += std::sqrt(dx * dx + dy * dy);
×
120
        }
121
    }
122

123
    // If total length is very small or zero, or only one point, just return the (fitted) first point
NEW
124
    if (total_length < 1e-6f || original_points.size() <= 1 || target_spacing <= 1e-6f) {
×
125
        if (!original_points.empty()) {
×
126
            // Evaluate polynomial at t=0 (or use the first point if no coeffs)
127
            if (!x_coeffs.empty() && !y_coeffs.empty()) {
×
128
                float x = static_cast<float>(evaluate_polynomial(x_coeffs, 0.0));
×
129
                float y = static_cast<float>(evaluate_polynomial(y_coeffs, 0.0));
×
130
                return Line2D({{x, y}});
×
131
            } else {
132
                return Line2D({original_points[0]});
×
133
            }
134
        } else {
135
            return {};
×
136
        }
137
    }
138

139
    // Determine number of samples based on target_spacing
NEW
140
    auto num_samples = static_cast<size_t>(std::max(2.0f, std::round(total_length / target_spacing)));
×
141

142
    std::vector<Point2D<float>> smoothed_line;
×
143
    smoothed_line.reserve(num_samples);
×
144

NEW
145
    for (size_t i = 0; i < num_samples; ++i) {
×
146
        double t = static_cast<double>(i) / static_cast<double>(num_samples - 1);// t in [0,1]
×
147
        float x = static_cast<float>(evaluate_polynomial(x_coeffs, t));
×
148
        float y = static_cast<float>(evaluate_polynomial(y_coeffs, t));
×
149
        smoothed_line.push_back({x, y});
×
150
    }
151
    return smoothed_line;
×
152
}
×
153

154
// Calculate the error between fitted polynomial and original points
155
std::vector<float> calculate_fitting_errors(std::vector<Point2D<float>> const & points,
×
156
                                            std::vector<double> const & x_coeffs,
157
                                            std::vector<double> const & y_coeffs) {
158
    if (points.empty()) {
×
159
        return {};
×
160
    }
161

162
    // Calculate t values using the helper function
163
    std::vector<double> t_values = compute_t_values(points);
×
164
    if (t_values.empty()) {
×
165
        return {};
×
166
    }
167

168
    std::vector<float> errors;
×
169
    errors.reserve(points.size());
×
170

171
    for (size_t i = 0; i < points.size(); ++i) {
×
172
        double fitted_x = evaluate_polynomial(x_coeffs, t_values[i]);
×
173
        double fitted_y = evaluate_polynomial(y_coeffs, t_values[i]);
×
174

175
        // Use squared error directly (no square root)
NEW
176
        float error_squared = static_cast<float>(std::pow(static_cast<double>(points[i].x) - fitted_x, 2) +
×
NEW
177
                              std::pow(static_cast<double>(points[i].y) - fitted_y, 2));
×
UNCOV
178
        errors.push_back(error_squared);
×
179
    }
180

181
    return errors;
×
182
}
×
183

184
// Recursive helper function for removing outliers
185
Line2D remove_outliers_recursive(Line2D const & points,
×
186
                                 float error_threshold_squared,
187
                                 int polynomial_order,
188
                                 int max_iterations) {
189
    if (points.size() < static_cast<size_t>(polynomial_order + 2) || max_iterations <= 0) {
×
190
        return points;// Base case: not enough points or max iterations reached
×
191
    }
192

193
    // Calculate t values for parameterization
NEW
194
    std::vector<double> t_values_recursive = compute_t_values(points);
×
NEW
195
    if (t_values_recursive.empty()) {
×
UNCOV
196
        return points;
×
197
    }
198

199
    // Extract x and y coordinates
NEW
200
    std::vector<double> x_coords_recursive;
×
NEW
201
    std::vector<double> y_coords_recursive;
×
202

NEW
203
    x_coords_recursive.reserve(points.size());
×
NEW
204
    y_coords_recursive.reserve(points.size());
×
205

206
    for (auto const & point: points) {
×
NEW
207
        x_coords_recursive.push_back(static_cast<double>(point.x));
×
NEW
208
        y_coords_recursive.push_back(static_cast<double>(point.y));
×
209
    }
210

211
    // Create Armadillo matrices
NEW
212
    arma::mat X(t_values_recursive.size(), static_cast<arma::uword>(polynomial_order) + 1);
×
NEW
213
    arma::vec X_vec(x_coords_recursive.data(), x_coords_recursive.size());
×
NEW
214
    arma::vec Y_vec(y_coords_recursive.data(), y_coords_recursive.size());
×
215

216
    // Build Vandermonde matrix
NEW
217
    for (size_t i = 0; i < t_values_recursive.size(); ++i) {
×
218
        for (int j = 0; j <= polynomial_order; ++j) {
×
NEW
219
            X(i, static_cast<arma::uword>(j)) = std::pow(t_values_recursive[i], static_cast<double>(j));
×
220
        }
221
    }
222

223
    // Solve least squares problems
NEW
224
    arma::vec x_coeffs_recursive;
×
NEW
225
    arma::vec y_coeffs_recursive;
×
NEW
226
    bool success_x = arma::solve(x_coeffs_recursive, X, X_vec);
×
NEW
227
    bool success_y = arma::solve(y_coeffs_recursive, X, Y_vec);
×
228

229
    if (!success_x || !success_y) {
×
230
        return points;// Failed to fit, return original points
×
231
    }
232

233
    // Calculate errors and filter points
234
    std::vector<Point2D<float>> filtered_points;
×
235
    filtered_points.reserve(points.size());
×
236
    bool any_points_removed = false;
×
237

238
    for (size_t i = 0; i < points.size(); ++i) {
×
239
        double fitted_x = 0.0;
×
240
        double fitted_y = 0.0;
×
241

242
        for (int j = 0; j <= polynomial_order; ++j) {
×
NEW
243
            fitted_x += x_coeffs_recursive(static_cast<arma::uword>(j)) * std::pow(t_values_recursive[i], static_cast<double>(j));
×
NEW
244
            fitted_y += y_coeffs_recursive(static_cast<arma::uword>(j)) * std::pow(t_values_recursive[i], static_cast<double>(j));
×
245
        }
246

247
        // Use squared error directly (no square root)
NEW
248
        float error_squared = static_cast<float>(std::pow(static_cast<double>(points[i].x) - fitted_x, 2) +
×
NEW
249
                              std::pow(static_cast<double>(points[i].y) - fitted_y, 2));
×
250

251
        // Keep point if error is below threshold
252
        if (error_squared <= error_threshold_squared) {
×
253
            filtered_points.push_back(points[i]);
×
254
        } else {
255
            any_points_removed = true;
×
256
        }
257
    }
258

259
    // If we filtered too many points, return original set
260
    if (filtered_points.size() < static_cast<size_t>(polynomial_order + 2)) {
×
261
        return points;
×
262
    }
263

264
    // If we removed points, recursively call with filtered points
265
    if (any_points_removed) {
×
266
        return remove_outliers_recursive(filtered_points, error_threshold_squared, polynomial_order, max_iterations - 1);
×
267
    } else {
268
        return filtered_points;// No more points to remove
×
269
    }
270
}
×
271

272
// Main outlier removal function using recursion
273
Line2D remove_outliers(Line2D const & points,
×
274
                       float error_threshold,
275
                       int polynomial_order) {
276
    if (points.size() < static_cast<size_t>(polynomial_order + 2)) {
×
277
        return points;// Not enough points to fit and filter
×
278
    }
279

280
    // Convert threshold to squared threshold to avoid square roots in comparisons
281
    float error_threshold_squared = error_threshold * error_threshold;
×
282

283
    // Call recursive helper with a reasonable iteration limit
284
    return remove_outliers_recursive(points, error_threshold_squared, polynomial_order, 10);
×
285
}
286

287
///////////////////////////////////////////////////////////////////////////////
288

289
std::shared_ptr<LineData> mask_to_line(MaskData const * mask_data, MaskToLineParameters const * params) {
×
290
    // Call the version with progress reporting but ignore progress
291
    return mask_to_line(mask_data, params, [](int) {});
×
292
}
293

294
std::shared_ptr<LineData> mask_to_line(MaskData const * mask_data,
×
295
                                       MaskToLineParameters const * params,
296
                                       ProgressCallback progressCallback) {
297

298
    auto line_map = std::map<TimeFrameIndex, std::vector<Line2D>>();
×
299

300
    // Use default parameters if none provided
301
    float reference_x = params ? params->reference_x : 0.0f;
×
302
    float reference_y = params ? params->reference_y : 0.0f;
×
303
    LinePointSelectionMethod method = params ? params->method : LinePointSelectionMethod::Skeletonize;
×
304
    int polynomial_order = params ? params->polynomial_order : 3;
×
305
    float error_threshold = params ? params->error_threshold : 5.0f;
×
306
    bool should_remove_outliers = params ? params->remove_outliers : true;
×
307
    int input_point_subsample_factor = params ? params->input_point_subsample_factor : 1;
×
308
    bool should_smooth_line = params ? params->should_smooth_line : false;
×
309
    float output_resolution = params ? params->output_resolution : 5.0f;
×
310

311
    std::cout << "reference_x: " << reference_x << std::endl;
×
312
    std::cout << "reference_y: " << reference_y << std::endl;
×
313
    std::cout << "method: " << static_cast<int>(method) << std::endl;
×
314
    std::cout << "polynomial_order: " << polynomial_order << std::endl;
×
315
    std::cout << "error_threshold: " << error_threshold << std::endl;
×
316
    std::cout << "should_remove_outliers: " << should_remove_outliers << std::endl;
×
317
    std::cout << "input_point_subsample_factor: " << input_point_subsample_factor << std::endl;
×
318
    std::cout << "should_smooth_line: " << should_smooth_line << std::endl;
×
319
    std::cout << "output_resolution: " << output_resolution << std::endl;
×
320

321
    Point2D<float> reference_point{reference_x, reference_y};
×
322

323
    // Initial progress
324
    progressCallback(0);
×
325

326
    // Count total masks to process for progress calculation
327
    size_t const total_masks = mask_data->size();
×
328

329
    if (total_masks == 0) {
×
330
        progressCallback(100);
×
331
        return std::make_shared<LineData>();// Nothing to process
×
332
    }
333

334
    // Create a binary image from the mask points
335
    ImageSize image_size = mask_data->getImageSize();
×
336
    if (image_size.width <= 0 || image_size.height <= 0) {
×
337
        // Use default size if mask data doesn't specify
338
        image_size.width = 256;
×
339
        image_size.height = 256;
×
340
    }
341

NEW
342
    std::vector<uint8_t> binary_image(static_cast<size_t>(image_size.width * image_size.height), 0);
×
343

344
    // Timing variables
345
    std::vector<long long> skeletonize_times;
×
346
    std::vector<long long> order_line_times;
×
347
    std::vector<long long> outlier_removal_times;
×
348
    std::vector<long long> map_insertion_times;
×
349
    std::vector<long long> smoothing_times;
×
350

351
    size_t processed_masks = 0;
×
352
    for (auto const & mask_time_pair: mask_data->getAllAsRange()) {
×
353
        auto time = mask_time_pair.time;
×
354
        auto const & masks = mask_time_pair.masks;
×
355

356
        if (masks.empty()) {
×
357
            continue;
×
358
        }
359

360
        // For now, just process the first mask at each time
361
        auto const & mask = masks[0];
×
362

363
        if (mask.empty()) {
×
364
            continue;
×
365
        }
366

367
        Line2D line_points;
×
368

369
        if (method == LinePointSelectionMethod::Skeletonize) {
×
370
            // Zero out the binary image
371
            std::fill(binary_image.begin(), binary_image.end(), 0);
×
372

373
            for (auto const & point: mask) {
×
NEW
374
                auto x = static_cast<int>(point.x);
×
NEW
375
                auto y = static_cast<int>(point.y);
×
376

377
                if (x >= 0 && x < image_size.width &&
×
378
                    y >= 0 && y < image_size.height) {
×
NEW
379
                    binary_image[static_cast<size_t>(y * image_size.width + x)] = 1;
×
380
                }
381
            }
382

383
            // Time skeletonization
384
            auto skeletonize_start = std::chrono::high_resolution_clock::now();
×
NEW
385
            auto skeleton = fast_skeletonize(binary_image, static_cast<size_t>(image_size.height), static_cast<size_t>(image_size.width));
×
386
            auto skeletonize_end = std::chrono::high_resolution_clock::now();
×
387
            skeletonize_times.push_back(
×
388
                    std::chrono::duration_cast<std::chrono::microseconds>(
×
389
                            skeletonize_end - skeletonize_start)
×
390
                            .count());
×
391

392
            // Time ordering step
393
            auto order_start = std::chrono::high_resolution_clock::now();
×
394
            line_points = order_line(skeleton, image_size, reference_point, input_point_subsample_factor);
×
395
            auto order_end = std::chrono::high_resolution_clock::now();
×
396
            order_line_times.push_back(
×
397
                    std::chrono::duration_cast<std::chrono::microseconds>(
×
398
                            order_end - order_start)
×
399
                            .count());
×
400
        } else {
×
401

402
            // Time ordering step for this method
403
            auto order_start = std::chrono::high_resolution_clock::now();
×
404
            line_points = order_line(mask, reference_point, input_point_subsample_factor);
×
405
            auto order_end = std::chrono::high_resolution_clock::now();
×
406
            order_line_times.push_back(
×
407
                    std::chrono::duration_cast<std::chrono::microseconds>(
×
408
                            order_end - order_start)
×
409
                            .count());
×
410
        }
411

412
        if (should_remove_outliers && line_points.size() > static_cast<size_t>(polynomial_order + 2)) {
×
413
            // Time outlier removal
414
            auto outlier_start = std::chrono::high_resolution_clock::now();
×
415
            line_points = remove_outliers(line_points, error_threshold, polynomial_order);
×
416
            auto outlier_end = std::chrono::high_resolution_clock::now();
×
417
            outlier_removal_times.push_back(
×
418
                    std::chrono::duration_cast<std::chrono::microseconds>(
×
419
                            outlier_end - outlier_start)
×
420
                            .count());
×
421
        }
422

423
        // Apply smoothing if requested and enough points exist
424
        if (should_smooth_line && line_points.size() > static_cast<size_t>(polynomial_order)) {// Need at least order+1 points
×
425
            auto smoothing_start = std::chrono::high_resolution_clock::now();
×
426
            ParametricCoefficients coeffs = fit_parametric_polynomials(line_points, polynomial_order);
×
427
            if (coeffs.success) {
×
428
                line_points = generate_smoothed_line(line_points, coeffs.x_coeffs, coeffs.y_coeffs, polynomial_order, output_resolution);
×
429
            } else {
430
                // Smoothing failed, fall back to resampling the existing (ordered, possibly outlier-removed) points
431
                if (!line_points.empty()) {
×
432
                    line_points = resample_line_points(line_points, output_resolution);
×
433
                }
434
            }
435
            auto smoothing_end = std::chrono::high_resolution_clock::now();
×
436
            smoothing_times.push_back(
×
437
                    std::chrono::duration_cast<std::chrono::microseconds>(
×
438
                            smoothing_end - smoothing_start)
×
439
                            .count());
×
440
        } else if (!line_points.empty()) {
×
441
            // If not smoothing (or not enough points for smoothing), apply resampling directly
442
            line_points = resample_line_points(line_points, output_resolution);
×
443
        }
444

445
        if (!line_points.empty()) {
×
446
            // Time map insertion
447
            auto insertion_start = std::chrono::high_resolution_clock::now();
×
448
            line_map[time].push_back(std::move(line_points));
×
449
            auto insertion_end = std::chrono::high_resolution_clock::now();
×
450
            map_insertion_times.push_back(
×
451
                    std::chrono::duration_cast<std::chrono::microseconds>(
×
452
                            insertion_end - insertion_start)
×
453
                            .count());
×
454
        }
455

456
        processed_masks++;
×
457

458
        // Print timing statistics every 1000 iterations or on the last iteration
459
        if (processed_masks % 1000 == 0 || processed_masks == total_masks) {
×
460
            if (!skeletonize_times.empty()) {
×
461
                double skeletonize_avg = std::accumulate(skeletonize_times.begin(), skeletonize_times.end(), 0.0) /
×
NEW
462
                                         static_cast<double>(skeletonize_times.size());
×
463
                std::cout << "Average skeletonization time: " << skeletonize_avg << " μs" << std::endl;
×
464
            }
465

466
            if (!order_line_times.empty()) {
×
467
                double order_avg = std::accumulate(order_line_times.begin(), order_line_times.end(), 0.0) /
×
NEW
468
                                   static_cast<double>(order_line_times.size());
×
469
                std::cout << "Average order_line time: " << order_avg << " μs" << std::endl;
×
470
            }
471

472
            if (!outlier_removal_times.empty()) {
×
473
                double outlier_avg = std::accumulate(outlier_removal_times.begin(), outlier_removal_times.end(), 0.0) /
×
NEW
474
                                     static_cast<double>(outlier_removal_times.size());
×
475
                std::cout << "Average outlier removal time: " << outlier_avg << " μs" << std::endl;
×
476
            }
477

478
            if (!map_insertion_times.empty()) {
×
479
                double insertion_avg = std::accumulate(map_insertion_times.begin(), map_insertion_times.end(), 0.0) /
×
NEW
480
                                       static_cast<double>(map_insertion_times.size());
×
481
                std::cout << "Average map insertion time: " << insertion_avg << " μs" << std::endl;
×
482
            }
483

484
            if (!smoothing_times.empty()) {
×
485
                double smoothing_avg = std::accumulate(smoothing_times.begin(), smoothing_times.end(), 0.0) /
×
NEW
486
                                       static_cast<double>(smoothing_times.size());
×
487
                std::cout << "Average smoothing time: " << smoothing_avg << " μs" << std::endl;
×
488
            }
489

490
            // Clear the vectors to only keep the last 1000 measurements
491
            if (processed_masks % 1000 == 0 && processed_masks < total_masks) {
×
492
                skeletonize_times.clear();
×
493
                order_line_times.clear();
×
494
                outlier_removal_times.clear();
×
495
                map_insertion_times.clear();
×
496
                smoothing_times.clear();
×
497
            }
498
        }
499

NEW
500
        int progress = static_cast<int>(std::round((static_cast<double>(processed_masks) / static_cast<double>(total_masks)) * 100.0));
×
501
        progressCallback(progress);
×
502
    }
×
503

504
    auto line_data = std::make_shared<LineData>(line_map);
×
505

506
    // Copy the image size from mask data to line data
507
    line_data->setImageSize(mask_data->getImageSize());
×
508

509
    progressCallback(100);
×
510

511
    return line_data;
×
512
}
×
513

514
///////////////////////////////////////////////////////////////////////////////// 
515

516
std::string MaskToLineOperation::getName() const {
×
517
    return "Convert Mask to Line";
×
518
}
519

520
std::type_index MaskToLineOperation::getTargetInputTypeIndex() const {
×
521
    return typeid(std::shared_ptr<MaskData>);
×
522
}
523

524
bool MaskToLineOperation::canApply(DataTypeVariant const & dataVariant) const {
×
525
    if (!std::holds_alternative<std::shared_ptr<MaskData>>(dataVariant)) {
×
526
        return false;
×
527
    }
528

529
    auto const * ptr_ptr = std::get_if<std::shared_ptr<MaskData>>(&dataVariant);
×
530
    return ptr_ptr && *ptr_ptr;
×
531
}
532

533
std::unique_ptr<TransformParametersBase> MaskToLineOperation::getDefaultParameters() const {
×
534
    return std::make_unique<MaskToLineParameters>();
×
535
}
536

537
DataTypeVariant MaskToLineOperation::execute(DataTypeVariant const & dataVariant,
×
538
                                             TransformParametersBase const * transformParameters) {
539
    // Call the version with progress reporting but ignore progress
540
    return execute(dataVariant, transformParameters, [](int) {});
×
541
}
542

543
DataTypeVariant MaskToLineOperation::execute(DataTypeVariant const & dataVariant,
×
544
                                             TransformParametersBase const * transformParameters,
545
                                             ProgressCallback progressCallback) {
546
    auto const * ptr_ptr = std::get_if<std::shared_ptr<MaskData>>(&dataVariant);
×
547

548
    if (!ptr_ptr || !(*ptr_ptr)) {
×
549
        std::cerr << "MaskToLineOperation::execute called with incompatible variant type or null data." << std::endl;
×
550
        return {};// Return empty variant
×
551
    }
552

553
    MaskData const * mask_raw_ptr = (*ptr_ptr).get();
×
554

555
    MaskToLineParameters const * typed_params = nullptr;
×
556
    if (transformParameters) {
×
557
        typed_params = dynamic_cast<MaskToLineParameters const *>(transformParameters);
×
558
        if (!typed_params) {
×
559
            std::cerr << "MaskToLineOperation::execute: Invalid parameter type" << std::endl;
×
560
        }
561
    }
562

563
    std::shared_ptr<LineData> result_line = mask_to_line(mask_raw_ptr, typed_params, progressCallback);
×
564

565
    // Handle potential failure from the calculation function
566
    if (!result_line) {
×
567
        std::cerr << "MaskToLineOperation::execute: 'mask_to_line' failed to produce a result." << std::endl;
×
568
        return {};// Return empty variant
×
569
    }
570

571
    std::cout << "MaskToLineOperation executed successfully." << std::endl;
×
572
    return result_line;
×
573
}
×
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