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

paulmthompson / WhiskerToolbox / 18477247352

13 Oct 2025 08:18PM UTC coverage: 72.391% (+0.4%) from 71.943%
18477247352

push

github

web-flow
Merge pull request #140 from paulmthompson/kdtree

Jules PR

164 of 287 new or added lines in 3 files covered. (57.14%)

350 existing lines in 9 files now uncovered.

51889 of 71679 relevant lines covered (72.39%)

63071.54 hits per line

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

77.03
/src/DataManager/transforms/Lines/Line_Alignment/line_alignment.cpp
1
#include "line_alignment.hpp"
2

3
#include "Lines/Line_Data.hpp"
4
#include "Media/Media_Data.hpp"
5
#include "CoreGeometry/line_geometry.hpp"
6
#include "Entity/EntityGroupManager.hpp"
7
#include "Entity/EntityTypes.hpp"
8

9
#include <algorithm>
10
#include <cmath>
11
#include <iostream>
12
#include <limits>
13
#include <map>
14
#include <vector>
15

16

17
template<typename T>
18
T get_pixel_value(Point2D<float> const & point,
30,847✔
19
                  std::vector<T> const & image_data,
20
                  ImageSize const & image_size) {
21
    int x = static_cast<int>(std::round(point.x));
30,847✔
22
    int y = static_cast<int>(std::round(point.y));
30,847✔
23

24
    // Check bounds
25
    if (x < 0 || x >= image_size.width || y < 0 || y >= image_size.height) {
30,847✔
26
        return 0;
1,224✔
27
    }
28

29
    size_t index = static_cast<size_t>(y * image_size.width + x);
29,623✔
30
    //size_t index = static_cast<size_t>(x * image_size.height + y);
31
    if (index < image_data.size()) {
29,623✔
32
        return image_data[index];
29,062✔
33
    }
34

35
    return 0;
561✔
36
}
37

38
// Calculate FWHM center point for a single vertex
39
template<typename T>
40
Point2D<float> calculate_fwhm_center(Point2D<float> const & vertex,
37✔
41
                                     Point2D<float> const & perpendicular_dir,
42
                                     int width,
43
                                     int perpendicular_range,
44
                                     std::vector<T> const & image_data,
45
                                     ImageSize const & image_size,
46
                                     FWHMApproach /*approach*/) {
47
    if (width <= 0) {
37✔
48
        return vertex;// Return original vertex if no width
1✔
49
    }
50

51
    std::vector<Point2D<float>> center_points;
36✔
52
    std::vector<float> intensities;
36✔
53
    std::vector<T> profile = std::vector<T>(perpendicular_range+1, 0);
108✔
54
    std::vector<float> distances = std::vector<float>(perpendicular_range+1, 0);
108✔
55

56
    // Sample multiple points along the width of the analysis strip
57
    for (int w = -width / 2; w <= width / 2; ++w) {
1,089✔
58
        // Calculate the sampling point along the width direction
59
        Point2D<float> width_dir{-perpendicular_dir.y, perpendicular_dir.x};// Perpendicular to perp dir
550✔
60

61
        Point2D<float> sample_start = {
550✔
62
                vertex.x + width_dir.x * static_cast<float>(w),
550✔
63
                vertex.y + width_dir.y * static_cast<float>(w)};
550✔
64

65
        // Sample intensity profile along the perpendicular direction
66
        profile.assign(perpendicular_range+1, 0);
550✔
67
        distances.assign(perpendicular_range+1, 0);
550✔
68

69
        // Sample up to perpendicular_range pixels in each direction along the perpendicular
70
        int index = 0;
550✔
71
        for (int d = -perpendicular_range / 2; d <= perpendicular_range / 2; ++d) {
28,182✔
72
            Point2D<float> sample_point = {
27,632✔
73
                    sample_start.x + perpendicular_dir.x * static_cast<float>(d),
27,632✔
74
                    sample_start.y + perpendicular_dir.y * static_cast<float>(d)};
27,632✔
75

76
            T intensity = get_pixel_value(sample_point, image_data, image_size);
27,632✔
77
            profile[index] = intensity;
27,632✔
78
            distances[index] = static_cast<float>(d);
27,632✔
79
            index++;
27,632✔
80
        }
81

82
        // Find the maximum intensity in the profile
83
        auto max_it = std::max_element(profile.begin(), profile.end());
550✔
84
        if (max_it == profile.end()) {
550✔
UNCOV
85
            continue;
×
86
        }
87

88
        T max_intensity = *max_it;
550✔
89
        if (max_intensity == 0) {
550✔
90
            continue;// Skip if no signal
47✔
91
        }
92

93
        // Find all positions with the maximum intensity
94
        std::vector<int> max_indices;
503✔
95
        for (size_t i = 0; i < profile.size(); ++i) {
25,738✔
96
            if (profile[i] == max_intensity) {
25,235✔
97
                max_indices.push_back(static_cast<int>(i));
767✔
98
            }
99
        }
100

101
        // Calculate the average position of all maximum points
102
        float avg_max_index = 0.0f;
503✔
103
        for (int index: max_indices) {
1,270✔
104
            avg_max_index += static_cast<float>(index);
767✔
105
        }
106
        avg_max_index /= static_cast<float>(max_indices.size());
503✔
107

108
        int max_offset = static_cast<int>(std::round(avg_max_index)) - perpendicular_range / 2;
503✔
109

110
        // Find minimum in profile
111
        auto min_it = std::min_element(profile.begin(), profile.end());
503✔
112
        if (min_it == profile.end()) {
503✔
UNCOV
113
            continue;
×
114
        }
115

116
        T min_intensity = *min_it;
503✔
117

118
        // Find the half-maximum threshold
119
        float half_max = (static_cast<float>(max_intensity) + static_cast<float>(min_intensity)) / 2.0f;
503✔
120

121
        // Find the left and right boundaries at half maximum, starting from the average max position
122
        int left_bound = max_offset;
503✔
123
        int right_bound = max_offset;
503✔
124

125
        // Work leftward from the average max position to find the first half-maximum
126
        int avg_max_index_int = static_cast<int>(std::round(avg_max_index));
503✔
127
        for (int i = avg_max_index_int; i >= 0; --i) {
1,138✔
128
            if (profile[static_cast<size_t>(i)] < half_max) {
1,138✔
129
                left_bound = i + 1 - perpendicular_range / 2;// +1 to include the last point >= half_max
503✔
130
                break;
503✔
131
            }
132
        }
133

134
        // Work rightward from the average max position to find the first half-maximum
135
        for (size_t i = static_cast<size_t>(avg_max_index_int); i < profile.size(); ++i) {
1,138✔
136
            if (profile[i] < half_max) {
1,127✔
137
                right_bound = static_cast<int>(i) - 1 - perpendicular_range / 2;// -1 to include the last point >= half_max
492✔
138
                break;
492✔
139
            }
140
        }
141

142
        // Calculate the center point of the FWHM region
143
        float center_offset = (static_cast<float>(left_bound) + static_cast<float>(right_bound)) / 2.0f;
503✔
144
        Point2D<float> center_point = {
503✔
145
                sample_start.x + perpendicular_dir.x * center_offset,
503✔
146
                sample_start.y + perpendicular_dir.y * center_offset};
503✔
147
        center_points.push_back(center_point);
503✔
148
        intensities.push_back(static_cast<float>(max_intensity));
503✔
149
    }
150

151
    // Calculate weighted average center point based on intensity
152
    if (center_points.empty()) {
36✔
153
        return vertex;// Return original vertex if no center points found
4✔
154
    }
155

156
    float total_weight = 0.0f;
32✔
157
    Point2D<float> weighted_sum{0.0f, 0.0f};
32✔
158

159
    for (size_t i = 0; i < center_points.size(); ++i) {
535✔
160
        float weight = intensities[i];
503✔
161
        weighted_sum.x += center_points[i].x * weight;
503✔
162
        weighted_sum.y += center_points[i].y * weight;
503✔
163
        total_weight += weight;
503✔
164
    }
165

166
    if (total_weight > 0) {
32✔
167
        return Point2D<float>{weighted_sum.x / total_weight, weighted_sum.y / total_weight};
32✔
168
    }
169

UNCOV
170
    return vertex;// Return original vertex if no valid weights
×
171
}
36✔
172

173
// Calculate FWHM profile extents for a single vertex
174
template<typename T>
175
Line2D calculate_fwhm_profile_extents(Point2D<float> const & vertex,
3✔
176
                                      Point2D<float> const & perpendicular_dir,
177
                                      int width,
178
                                      int perpendicular_range,
179
                                      std::vector<T> const & image_data,
180
                                      ImageSize const & image_size,
181
                                      FWHMApproach /*approach*/) {
182
    if (width <= 0) {
3✔
183
        // Return a line with just the original vertex repeated
184
        Line2D debug_line;
×
185
        debug_line.push_back(vertex);
×
186
        debug_line.push_back(vertex);
×
187
        debug_line.push_back(vertex);
×
UNCOV
188
        return debug_line;
×
UNCOV
189
    }
×
190

191
    std::vector<Point2D<float>> left_extents;
3✔
192
    std::vector<Point2D<float>> right_extents;
3✔
193
    std::vector<Point2D<float>> max_points;
3✔
194
    std::vector<float> intensities;
3✔
195

196
    std::vector<T> profile = std::vector<T>(perpendicular_range+1, 0);
9✔
197
    std::vector<float> distances = std::vector<float>(perpendicular_range+1, 0);
9✔
198

199
    // Sample multiple points along the width of the analysis strip
200
    for (int w = -width / 2; w <= width / 2; ++w) {
128✔
201
        // Calculate the sampling point along the width direction
202
        Point2D<float> width_dir{-perpendicular_dir.y, perpendicular_dir.x};// Perpendicular to perp dir
63✔
203

204
        Point2D<float> sample_start = {
63✔
205
                vertex.x + width_dir.x * static_cast<float>(w),
63✔
206
                vertex.y + width_dir.y * static_cast<float>(w)};
63✔
207

208
        // Sample intensity profile along the perpendicular direction
209
        profile.assign(perpendicular_range+1, 0);
63✔
210
        distances.assign(perpendicular_range+1, 0);
63✔
211

212
        // Sample up to perpendicular_range pixels in each direction along the perpendicular
213
        int index = 0;
63✔
214
        for (int d = -perpendicular_range / 2; d <= perpendicular_range / 2; ++d) {
3,276✔
215
            Point2D<float> sample_point = {
3,213✔
216
                    sample_start.x + perpendicular_dir.x * static_cast<float>(d),
3,213✔
217
                    sample_start.y + perpendicular_dir.y * static_cast<float>(d)};
3,213✔
218

219
            T intensity = get_pixel_value(sample_point, image_data, image_size);
3,213✔
220
            profile[index] = intensity;
3,213✔
221
            distances[index] = static_cast<float>(d);
3,213✔
222
            index++;
3,213✔
223
        }
224

225
        // Find the maximum intensity in the profile
226
        auto max_it = std::max_element(profile.begin(), profile.end());
63✔
227
        if (max_it == profile.end()) {
63✔
UNCOV
228
            continue;
×
229
        }
230

231
        T max_intensity = *max_it;
63✔
232
        if (max_intensity == 0) {
63✔
233
            continue;// Skip if no signal
1✔
234
        }
235

236
        // Find all positions with the maximum intensity
237
        std::vector<int> max_indices;
62✔
238
        for (size_t i = 0; i < profile.size(); ++i) {
3,224✔
239
            if (profile[i] == max_intensity) {
3,162✔
240
                max_indices.push_back(static_cast<int>(i));
62✔
241
            }
242
        }
243

244
        // Calculate the average position of all maximum points
245
        float avg_max_index = 0.0f;
62✔
246
        for (int index: max_indices) {
124✔
247
            avg_max_index += static_cast<float>(index);
62✔
248
        }
249
        avg_max_index /= static_cast<float>(max_indices.size());
62✔
250

251
        int max_offset = static_cast<int>(std::round(avg_max_index)) - perpendicular_range / 2;
62✔
252

253
        // Calculate the maximum point location
254
        Point2D<float> max_point = {
62✔
255
                sample_start.x + perpendicular_dir.x * static_cast<float>(max_offset),
62✔
256
                sample_start.y + perpendicular_dir.y * static_cast<float>(max_offset)};
62✔
257

258
        // Find minimum in profile
259
        auto min_it = std::min_element(profile.begin(), profile.end());
62✔
260
        if (min_it == profile.end()) {
62✔
UNCOV
261
            continue;
×
262
        }
263

264
        T min_intensity = *min_it;
62✔
265

266
        // Find the half-maximum threshold
267
        float half_max = (static_cast<float>(max_intensity) + static_cast<float>(min_intensity)) / 2.0f;
62✔
268

269
        // Find the left and right boundaries at half maximum, starting from the average max position
270
        int left_bound = max_offset;
62✔
271
        int right_bound = max_offset;
62✔
272

273
        // Work leftward from the average max position to find the first half-maximum
274
        int avg_max_index_int = static_cast<int>(std::round(avg_max_index));
62✔
275
        for (int i = avg_max_index_int; i >= 0; --i) {
124✔
276
            if (profile[static_cast<size_t>(i)] < half_max) {
124✔
277
                left_bound = i + 1 - perpendicular_range / 2;// +1 to include the last point >= half_max
62✔
278
                break;
62✔
279
            }
280
        }
281

282
        // Work rightward from the average max position to find the first half-maximum
283
        for (size_t i = static_cast<size_t>(avg_max_index_int); i < profile.size(); ++i) {
124✔
284
            if (profile[i] < half_max) {
124✔
285
                right_bound = static_cast<int>(i) - 1 - perpendicular_range / 2;// -1 to include the last point >= half_max
62✔
286
                break;
62✔
287
            }
288
        }
289

290
        // Calculate the left and right extent points
291
        Point2D<float> left_extent = {
62✔
292
                sample_start.x + perpendicular_dir.x * static_cast<float>(left_bound),
62✔
293
                sample_start.y + perpendicular_dir.y * static_cast<float>(left_bound)};
62✔
294

295
        Point2D<float> right_extent = {
62✔
296
                sample_start.x + perpendicular_dir.x * static_cast<float>(right_bound),
62✔
297
                sample_start.y + perpendicular_dir.y * static_cast<float>(right_bound)};
62✔
298

299
        left_extents.push_back(left_extent);
62✔
300
        right_extents.push_back(right_extent);
62✔
301
        max_points.push_back(max_point);
62✔
302
        intensities.push_back(static_cast<float>(max_intensity));
62✔
303
    }
304

305
    // Calculate weighted average extents based on intensity
306
    if (left_extents.empty()) {
3✔
307
        // Return a line with just the original vertex repeated
308
        Line2D debug_line;
×
309
        debug_line.push_back(vertex);
×
310
        debug_line.push_back(vertex);
×
311
        debug_line.push_back(vertex);
×
UNCOV
312
        return debug_line;
×
UNCOV
313
    }
×
314

315
    float total_weight = 0.0f;
3✔
316
    Point2D<float> weighted_left_sum{0.0f, 0.0f};
3✔
317
    Point2D<float> weighted_right_sum{0.0f, 0.0f};
3✔
318
    Point2D<float> weighted_max_sum{0.0f, 0.0f};
3✔
319

320
    for (size_t i = 0; i < left_extents.size(); ++i) {
65✔
321
        float weight = intensities[i];
62✔
322
        weighted_left_sum.x += left_extents[i].x * weight;
62✔
323
        weighted_left_sum.y += left_extents[i].y * weight;
62✔
324
        weighted_right_sum.x += right_extents[i].x * weight;
62✔
325
        weighted_right_sum.y += right_extents[i].y * weight;
62✔
326
        weighted_max_sum.x += max_points[i].x * weight;
62✔
327
        weighted_max_sum.y += max_points[i].y * weight;
62✔
328
        total_weight += weight;
62✔
329
    }
330

331
    if (total_weight > 0) {
3✔
332
        Point2D<float> avg_left = {weighted_left_sum.x / total_weight, weighted_left_sum.y / total_weight};
3✔
333
        Point2D<float> avg_right = {weighted_right_sum.x / total_weight, weighted_right_sum.y / total_weight};
3✔
334
        Point2D<float> avg_max = {weighted_max_sum.x / total_weight, weighted_max_sum.y / total_weight};
3✔
335

336
        Line2D debug_line;
3✔
337
        debug_line.push_back(avg_left);
3✔
338
        debug_line.push_back(avg_max);
3✔
339
        debug_line.push_back(avg_right);
3✔
340
        return debug_line;
3✔
341
    }
3✔
342

343
    // Return a line with just the original vertex repeated
344
    Line2D debug_line;
×
345
    debug_line.push_back(vertex);
×
346
    debug_line.push_back(vertex);
×
UNCOV
347
    debug_line.push_back(vertex);
×
UNCOV
348
    return debug_line;
×
349
}
3✔
350

351
// Helper function to process lines with image data of any type
352
template<typename T>
353
std::vector<Line2D> processLinesWithImageData(std::vector<Line2D> const & lines,
7✔
354
                                               std::vector<T> const & image_data,
355
                                               ImageSize const & image_size,
356
                                               int width,
357
                                               int perpendicular_range,
358
                                               FWHMApproach approach,
359
                                               LineAlignmentOutputMode output_mode) {
360
    std::vector<Line2D> aligned_lines;
7✔
361
    
362
    for (auto const & line : lines) {
14✔
363
        if (line.size() < 3) {
7✔
364
            // Skip lines with fewer than 3 vertices
UNCOV
365
            aligned_lines.push_back(line);
×
UNCOV
366
            continue;
×
367
        }
368

369
        if (output_mode == LineAlignmentOutputMode::FWHM_PROFILE_EXTENTS) {
7✔
370
            // Debug mode: create a debug line for each vertex
371
            for (size_t i = 0; i < line.size(); ++i) {
7✔
372
                Point2D<float> vertex = line[i];
3✔
373

374
                // Calculate perpendicular direction
375
                Point2D<float> perp_dir = calculate_perpendicular_direction(line, i);
3✔
376

377
                if (perp_dir.x == 0.0f && perp_dir.y == 0.0f) {
3✔
378
                    // If we can't calculate a perpendicular direction, create a debug line with just the vertex repeated
379
                    Line2D debug_line;
×
380
                    debug_line.push_back(vertex);
×
381
                    debug_line.push_back(vertex);
×
382
                    debug_line.push_back(vertex);
×
383
                    aligned_lines.push_back(debug_line);
×
UNCOV
384
                    continue;
×
UNCOV
385
                }
×
386

387
                // Debug mode: calculate FWHM profile extents
388
                Line2D debug_line = calculate_fwhm_profile_extents(vertex,
3✔
389
                                                                   perp_dir,
390
                                                                   width,
391
                                                                   perpendicular_range,
392
                                                                   image_data,
393
                                                                   image_size,
394
                                                                   approach);
395
                aligned_lines.push_back(debug_line);
3✔
396
            }
397
        } else {
398
            // Normal mode: create a single aligned line
399
            Line2D aligned_line;
6✔
400

401
            // Process each vertex in the line
402
            for (size_t i = 0; i < line.size(); ++i) {
24✔
403
                Point2D<float> vertex = line[i];
18✔
404

405
                // Calculate perpendicular direction
406
                Point2D<float> perp_dir = calculate_perpendicular_direction(line, i);
18✔
407

408
                if (perp_dir.x == 0.0f && perp_dir.y == 0.0f) {
18✔
409
                    // If we can't calculate a perpendicular direction, keep the original vertex
UNCOV
410
                    aligned_line.push_back(vertex);
×
UNCOV
411
                    continue;
×
412
                }
413

414
                // Normal mode: calculate FWHM center point
415
                Point2D<float> aligned_vertex = calculate_fwhm_center(vertex,
18✔
416
                                                                      perp_dir,
417
                                                                      width,
418
                                                                      perpendicular_range,
419
                                                                      image_data,
420
                                                                      image_size,
421
                                                                      approach);
422

423
                // Ensure the aligned vertex stays within image bounds
424
                aligned_vertex.x = std::max(0.0f, std::min(static_cast<float>(image_size.width - 1), aligned_vertex.x));
18✔
425
                aligned_vertex.y = std::max(0.0f, std::min(static_cast<float>(image_size.height - 1), aligned_vertex.y));
18✔
426

427
                aligned_line.push_back(aligned_vertex);
18✔
428
            }
429

430
            aligned_lines.push_back(aligned_line);
6✔
431
        }
6✔
432
    }
433
    
434
    return aligned_lines;
7✔
UNCOV
435
}
×
436

437
///////////////////////////////////////////////////////////////////////////////
438

439
std::string LineAlignmentOperation::getName() const {
148✔
440
    return "Line Alignment to Bright Features";
444✔
441
}
442

443
std::type_index LineAlignmentOperation::getTargetInputTypeIndex() const {
148✔
444
    return typeid(std::shared_ptr<LineData>);
148✔
445
}
446

447
bool LineAlignmentOperation::canApply(DataTypeVariant const & dataVariant) const {
3✔
448
    if (!std::holds_alternative<std::shared_ptr<LineData>>(dataVariant)) {
3✔
UNCOV
449
        return false;
×
450
    }
451

452
    auto const * ptr_ptr = std::get_if<std::shared_ptr<LineData>>(&dataVariant);
3✔
453

454
    return ptr_ptr && *ptr_ptr;
3✔
455
}
456

457
std::unique_ptr<TransformParametersBase> LineAlignmentOperation::getDefaultParameters() const {
3✔
458
    // Create default parameters with null group manager
459
    // The EntityGroupManager must be set via setGroupManager() before execution
460
    return std::make_unique<LineAlignmentParameters>();
3✔
461
}
462

UNCOV
463
DataTypeVariant LineAlignmentOperation::execute(DataTypeVariant const & dataVariant,
×
464
                                                TransformParametersBase const * transformParameters) {
UNCOV
465
    return execute(dataVariant, transformParameters, [](int) {});
×
466
}
467

468
DataTypeVariant LineAlignmentOperation::execute(DataTypeVariant const & dataVariant,
3✔
469
                                                TransformParametersBase const * transformParameters,
470
                                                ProgressCallback progressCallback) {
471
    auto const * ptr_ptr = std::get_if<std::shared_ptr<LineData>>(&dataVariant);
3✔
472
    if (!ptr_ptr || !(*ptr_ptr)) {
3✔
UNCOV
473
        std::cerr << "LineAlignmentOperation::execute: Incompatible variant type or null data." << std::endl;
×
UNCOV
474
        if (progressCallback) progressCallback(100);
×
UNCOV
475
        return {};
×
476
    }
477

478
    auto line_data = *ptr_ptr;
3✔
479

480
    auto const * typed_params =
481
            transformParameters ? dynamic_cast<LineAlignmentParameters const *>(transformParameters) : nullptr;
3✔
482

483
    // Check if we have valid parameters
484
    if (!typed_params) {
3✔
485
        std::cerr << "LineAlignmentOperation::execute: Invalid parameters provided. Operation requires LineAlignmentParameters." << std::endl;
×
486
        if (progressCallback) progressCallback(100);
×
UNCOV
487
        return {};
×
488
    }
489

490
    // Check if group manager is valid (only required if grouping is enabled)
491
    if (typed_params->enable_grouping && !typed_params->hasValidGroupManager()) {
3✔
UNCOV
492
        std::cerr << "LineAlignmentOperation::execute: EntityGroupManager is required when grouping is enabled but not set. Call setGroupManager() on parameters before execution." << std::endl;
×
UNCOV
493
        if (progressCallback) progressCallback(100);
×
UNCOV
494
        return {};
×
495
    }
496

497
    // Auto-find media data if not provided in parameters
498
    std::shared_ptr<MediaData> media_data;
3✔
499
    if (typed_params->media_data) {
3✔
500
        media_data = typed_params->media_data;
3✔
501
    } else {
UNCOV
502
        std::cerr << "LineAlignmentOperation::execute: No media data provided. Operation requires media data to align lines to bright features." << std::endl;
×
UNCOV
503
        if (progressCallback) progressCallback(100);
×
UNCOV
504
        return {};
×
505
    }
506

507
    if (progressCallback) progressCallback(0);
3✔
508

509
    // Use parameters from the typed_params object
510
    int width = typed_params->width;
3✔
511
    int perpendicular_range = typed_params->perpendicular_range;
3✔
512
    bool use_processed_data = typed_params->use_processed_data;
3✔
513
    FWHMApproach approach = typed_params->approach;
3✔
514
    LineAlignmentOutputMode output_mode = typed_params->output_mode;
3✔
515

516
    std::shared_ptr<LineData> result = line_alignment(
3✔
517
            line_data.get(),
3✔
518
            media_data.get(),
519
            width,
520
            perpendicular_range,
521
            use_processed_data,
522
            approach,
523
            output_mode,
524
            typed_params,
525
            progressCallback);
6✔
526

527
    if (!result) {
3✔
UNCOV
528
        std::cerr << "LineAlignmentOperation::execute: Alignment failed." << std::endl;
×
UNCOV
529
        return {};
×
530
    }
531

532
    std::cout << "LineAlignmentOperation executed successfully." << std::endl;
3✔
533
    return result;
3✔
534
}
3✔
535

536
std::shared_ptr<LineData> line_alignment(LineData const * line_data,
4✔
537
                                         MediaData * media_data,
538
                                         int width,
539
                                         int perpendicular_range,
540
                                         bool use_processed_data,
541
                                         FWHMApproach approach,
542
                                         LineAlignmentOutputMode output_mode,
543
                                         LineAlignmentParameters const * params) {
544
    return line_alignment(line_data, media_data, width, perpendicular_range, use_processed_data, approach, output_mode, params, [](int) {});
4✔
545
}
546

547
std::shared_ptr<LineData> line_alignment(LineData const * line_data,
7✔
548
                                         MediaData * media_data,
549
                                         int width,
550
                                         int perpendicular_range,
551
                                         bool use_processed_data,
552
                                         FWHMApproach approach,
553
                                         LineAlignmentOutputMode output_mode,
554
                                         LineAlignmentParameters const * params,
555
                                         ProgressCallback progressCallback) {
556
    if (!line_data || !media_data) {
7✔
UNCOV
557
        std::cerr << "LineAlignment: Null LineData or MediaData provided." << std::endl;
×
UNCOV
558
        if (progressCallback) progressCallback(100);
×
UNCOV
559
        return std::make_shared<LineData>();
×
560
    }
561

562
    // Check if line data has at least 3 vertices
563
    auto line_times = line_data->getTimesWithData();
7✔
564
    if (line_times.empty()) {
7✔
UNCOV
565
        if (progressCallback) progressCallback(100);
×
UNCOV
566
        return std::make_shared<LineData>();
×
567
    }
568

569
    // Create new LineData for the aligned lines
570
    auto aligned_line_data = std::make_shared<LineData>();
7✔
571
    aligned_line_data->setImageSize(line_data->getImageSize());
7✔
572

573
    // Check if group manager is valid (only required if grouping is enabled)
574
    if (params && params->enable_grouping && !params->hasValidGroupManager()) {
7✔
UNCOV
575
        std::cerr << "line_alignment: EntityGroupManager is required when grouping is enabled but not set. Call setGroupManager() on parameters before execution." << std::endl;
×
UNCOV
576
        if (progressCallback) progressCallback(100);
×
UNCOV
577
        return std::make_shared<LineData>();
×
578
    }
579

580
    // Set up grouping if enabled and we have a group manager
581
    std::map<size_t, std::uint64_t> vertex_to_group_map; // Maps vertex index to group ID
7✔
582
    bool grouping_enabled = params && params->enable_grouping && params->getGroupManager();
7✔
583
    EntityGroupManager* group_manager = grouping_enabled ? params->getGroupManager() : nullptr;
7✔
584

585
    //aligned_line_data->setIdentityContext(line_data->getIdentityContext());
586
    
587
    // Pre-create groups for all vertices if grouping is enabled and we're in FWHM mode
588
    if (grouping_enabled && params->output_mode == LineAlignmentOutputMode::FWHM_PROFILE_EXTENTS) {
7✔
589
        // Determine the maximum number of vertices by checking all time frames
UNCOV
590
        size_t max_vertices = 0;
×
591
        for (auto time: line_times) {
×
UNCOV
592
            auto const & lines = line_data->getAtTime(time);
×
UNCOV
593
            for (auto const & line: lines) {
×
UNCOV
594
                max_vertices = std::max(max_vertices, line.size());
×
595
            }
596
        }
597
        
598
        // Create groups for all possible vertex indices
UNCOV
599
        for (size_t vertex_idx = 0; vertex_idx < max_vertices; ++vertex_idx) {
×
UNCOV
600
            std::string group_name = params->group_prefix + std::to_string(vertex_idx);
×
UNCOV
601
            std::string group_desc = params->group_description + " " + std::to_string(vertex_idx);
×
UNCOV
602
            std::uint64_t group_id = group_manager->createGroup(group_name, group_desc);
×
UNCOV
603
            vertex_to_group_map[vertex_idx] = group_id;
×
UNCOV
604
        }
×
605
    }
606

607
    size_t total_time_points = line_times.size();
7✔
608
    size_t processed_time_points = 0;
7✔
609
    if (progressCallback) progressCallback(0);
7✔
610

611
    // Process each time that has line data
612
    for (auto time: line_times) {
14✔
613
        // Get lines at this time
614
        auto const & lines = line_data->getAtTime(time);
7✔
615
        if (lines.empty()) {
7✔
UNCOV
616
            continue;
×
617
        }
618

619
        // Get image size
620
        ImageSize image_size = media_data->getImageSize();
7✔
621

622
        // Process each line based on the media data type
623
        std::vector<Line2D> aligned_lines;
7✔
624
        
625
        // Check if media data is 8-bit or 32-bit and process accordingly
626
        if (media_data->is8Bit()) {
7✔
627
            // Get 8-bit image data
628
            std::vector<uint8_t> image_data;
5✔
629
            if (use_processed_data) {
5✔
630
                image_data = media_data->getProcessedData8(static_cast<int>(time.getValue()));
3✔
631
            } else {
632
                image_data = media_data->getRawData8(static_cast<int>(time.getValue()));
2✔
633
            }
634

635
            if (image_data.empty()) {
5✔
UNCOV
636
                continue;
×
637
            }
638

639
            // Process lines with 8-bit data
640
            aligned_lines = processLinesWithImageData(lines, image_data, image_size, 
10✔
641
                                                    width, perpendicular_range, approach, output_mode);
5✔
642
        } else {
5✔
643
            // Get 32-bit image data
644
            std::vector<float> image_data;
2✔
645
            if (use_processed_data) {
2✔
UNCOV
646
                image_data = media_data->getProcessedData32(static_cast<int>(time.getValue()));
×
647
            } else {
648
                image_data = media_data->getRawData32(static_cast<int>(time.getValue()));
2✔
649
            }
650

651
            if (image_data.empty()) {
2✔
UNCOV
652
                continue;
×
653
            }
654

655
            // Process lines with 32-bit data
656
            aligned_lines = processLinesWithImageData(lines, image_data, image_size, 
4✔
657
                                                    width, perpendicular_range, approach, output_mode);
2✔
658
        }
2✔
659

660
        // Add the aligned lines to the new LineData
661
        for (auto const & aligned_line: aligned_lines) {
16✔
662
            aligned_line_data->addAtTime(time, aligned_line, false);
9✔
663
        }
664
        
665
        // Handle grouping for FWHM profile extents mode
666
        if (grouping_enabled && output_mode == LineAlignmentOutputMode::FWHM_PROFILE_EXTENTS) {
7✔
667
            // Get the EntityIds for the lines we just added
UNCOV
668
            auto const & entity_ids = aligned_line_data->getEntityIdsAtTime(time);
×
669
            
670
            // For FWHM mode, each line corresponds to a vertex from the original line
UNCOV
671
            for (size_t line_idx = 0; line_idx < entity_ids.size(); ++line_idx) {
×
UNCOV
672
                EntityId entity_id = entity_ids[line_idx];
×
UNCOV
673
                size_t vertex_index = line_idx; // In FWHM mode, lines are created per vertex
×
674
                
675
                // Add this entity to the appropriate group (groups are already created)
UNCOV
676
                if (vertex_to_group_map.find(vertex_index) != vertex_to_group_map.end()) {
×
UNCOV
677
                    std::uint64_t group_id = vertex_to_group_map[vertex_index];
×
UNCOV
678
                    group_manager->addEntityToGroup(group_id, entity_id);
×
679
                }
680
            }
681
        }
682

683
        processed_time_points++;
7✔
684
        if (progressCallback) {
7✔
685
            int current_progress = static_cast<int>(std::round(static_cast<double>(processed_time_points) / static_cast<double>(total_time_points) * 100.0));
7✔
686
            progressCallback(current_progress);
7✔
687
        }
688
    }
7✔
689

690
    // Notify group manager of changes if grouping was enabled
691
    if (grouping_enabled && group_manager) {
7✔
UNCOV
692
        group_manager->notifyGroupsChanged();
×
693
    }
694

695
    if (progressCallback) progressCallback(100);
7✔
696
    return aligned_line_data;
7✔
697
}
7✔
698

699
template uint8_t get_pixel_value<uint8_t>(Point2D<float> const & point,
700
                                          std::vector<uint8_t> const & image_data,
701
                                          ImageSize const & image_size);
702

703
template float get_pixel_value<float>(Point2D<float> const & point,
704
                                      std::vector<float> const & image_data,
705
                                      ImageSize const & image_size);
706

707

708
template Point2D<float> calculate_fwhm_center<uint8_t>(Point2D<float> const & vertex,
709
                                                       Point2D<float> const & perpendicular_dir,
710
                                                       int width,
711
                                                       int perpendicular_range,
712
                                                       std::vector<uint8_t> const & image_data,
713
                                                       ImageSize const & image_size,
714
                                                       FWHMApproach approach = FWHMApproach::PEAK_WIDTH_HALF_MAX);
715
template Point2D<float> calculate_fwhm_center<float>(Point2D<float> const & vertex,
716
                                                     Point2D<float> const & perpendicular_dir,
717
                                                     int width,
718
                                                     int perpendicular_range,
719
                                                     std::vector<float> const & image_data,
720
                                                     ImageSize const & image_size,
721
                                                     FWHMApproach approach = FWHMApproach::PEAK_WIDTH_HALF_MAX);
722

723

724
template Line2D calculate_fwhm_profile_extents<uint8_t>(Point2D<float> const & vertex,
725
                                                        Point2D<float> const & perpendicular_dir,
726
                                                        int width,
727
                                                        int perpendicular_range,
728
                                                        std::vector<uint8_t> const & image_data,
729
                                                        ImageSize const & image_size,
730
                                                        FWHMApproach approach = FWHMApproach::PEAK_WIDTH_HALF_MAX);
731
template Line2D calculate_fwhm_profile_extents<float>(Point2D<float> const & vertex,
732
                                                      Point2D<float> const & perpendicular_dir,
733
                                                      int width,
734
                                                      int perpendicular_range,
735
                                                      std::vector<float> const & image_data,
736
                                                      ImageSize const & image_size,
737
                                                      FWHMApproach approach = FWHMApproach::PEAK_WIDTH_HALF_MAX);
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