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

paulmthompson / WhiskerToolbox / 17493011716

05 Sep 2025 12:22PM UTC coverage: 71.014% (+0.005%) from 71.009%
17493011716

push

github

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

34483 of 48558 relevant lines covered (71.01%)

1304.22 hits per line

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

99.35
/src/DataManager/transforms/Lines/Line_Alignment/line_alignment.test.cpp
1
#include "transforms/Lines/Line_Alignment/line_alignment.hpp"
2
#include "CoreGeometry/Image.hpp"
3
#include "CoreGeometry/ImageSize.hpp"
4
#include "CoreGeometry/lines.hpp"
5
#include "CoreGeometry/points.hpp"
6
#include "Lines/Line_Data.hpp"
7
#include "Media/Media_Data.hpp"
8
#include "TimeFrame/TimeFrame.hpp"
9
#include "transforms/Lines/Line_Alignment/mock_media_data.hpp"
10

11
#include <catch2/catch_test_macros.hpp>
12
#include <catch2/matchers/catch_matchers_floating_point.hpp>
13

14
#include <cmath>
15
#include <type_traits>
16
#include <vector>
17

18
TEST_CASE("Data Transform: Line Alignment - FWHM displacement calculation - Core functionality", "[line][alignment][fwhm][transform]") {
14✔
19

20
    SECTION("Simple bright line detection") {
14✔
21
        // Create a simple test image with a bright horizontal line
22
        ImageSize image_size{100, 100};
1✔
23
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
24

25
        // Create a bright horizontal line at y=50
26
        for (int x = 0; x < 100; ++x) {
101✔
27
            image_data[50 * 100 + x] = 255;// Bright white line
100✔
28
        }
29

30
        // Test vertex at (50, 50) with perpendicular direction pointing up
31
        Point2D<float> vertex{50.0f, 50.0f};
1✔
32
        Point2D<float> perp_dir{0.0f, -1.0f};// Pointing up
1✔
33

34
        Point2D<float> center_point = calculate_fwhm_center(
1✔
35
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
36

37
        // Should find the bright line and return center point at (50, 50) (already on the line)
38
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
39
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
40
    }
15✔
41

42
    SECTION("Coordinate system verification") {
14✔
43
        // Create a test image to verify coordinate system
44
        ImageSize image_size{10, 10};
1✔
45
        std::vector<uint8_t> image_data(100, 0);// 10x10 = 100 pixels
3✔
46

47
        // Set pixel at (5, 3) to white
48
        image_data[3 * 10 + 5] = 255;// y=3, x=5
1✔
49

50
        // Test that get_pixel_value returns correct value
51
        Point2D<float> test_point{5.0f, 3.0f};
1✔
52
        uint8_t pixel_value = get_pixel_value(test_point, image_data, image_size);
1✔
53
        REQUIRE(pixel_value == 255);
1✔
54

55
        // Test that get_pixel_value returns 0 for other positions
56
        Point2D<float> test_point2{4.0f, 3.0f};
1✔
57
        uint8_t pixel_value2 = get_pixel_value(test_point2, image_data, image_size);
1✔
58
        REQUIRE(pixel_value2 == 0);
1✔
59
    }
15✔
60

61
    SECTION("Bright line detection with offset") {
14✔
62
        // Create a test image with a bright horizontal line
63
        ImageSize image_size{100, 100};
1✔
64
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
65

66
        // Create a bright horizontal line at y=60
67
        for (int x = 0; x < 100; ++x) {
101✔
68
            image_data[60 * 100 + x] = 255;// Bright white line
100✔
69
        }
70

71
        // Test vertex at (50, 50) with perpendicular direction pointing up
72
        Point2D<float> vertex{50.0f, 50.0f};
1✔
73
        Point2D<float> perp_dir{0.0f, 1.0f};// Pointing down
1✔
74

75
        Point2D<float> center_point = calculate_fwhm_center(
1✔
76
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
77

78
        // Should find the bright line at y=60 and return center point at (50, 60)
79
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
80
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
81
    }
15✔
82

83
    SECTION("Bright line detection with offset and thickness") {
14✔
84
        // Create a test image with a bright horizontal line with thickness
85
        ImageSize image_size{100, 100};
1✔
86
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
87

88
        // Create a bright horizontal line at y=60 with thickness of 3 pixels
89
        for (int x = 0; x < 100; ++x) {
101✔
90
            // Line spans from y=59 to y=61 (3 pixels thick)
91
            for (int y = 59; y <= 61; ++y) {
400✔
92
                if (y >= 0 && y < 100) {
300✔
93
                    image_data[y * 100 + x] = 255;// Bright white line
300✔
94
                }
95
            }
96
        }
97

98
        // Test vertex at (50, 50) with perpendicular direction pointing down
99
        Point2D<float> vertex{50.0f, 50.0f};
1✔
100
        Point2D<float> perp_dir{0.0f, 1.0f};// Pointing down
1✔
101

102
        Point2D<float> center_point = calculate_fwhm_center(
1✔
103
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
104

105
        // Should find the center of the thick bright line at y=60 and return center point at (50, 60)
106
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
107
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
108
    }
15✔
109

110
    SECTION("Bright line detection with varying thickness") {
14✔
111
        // Create a test image with a bright horizontal line with varying thickness
112
        ImageSize image_size{100, 100};
1✔
113
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
114

115
        // Create a bright horizontal line at y=60 with thickness of 5 pixels
116
        for (int x = 0; x < 100; ++x) {
101✔
117
            // Line spans from y=58 to y=62 (5 pixels thick)
118
            for (int y = 58; y <= 62; ++y) {
600✔
119
                if (y >= 0 && y < 100) {
500✔
120
                    image_data[y * 100 + x] = 255;// Bright white line
500✔
121
                }
122
            }
123
        }
124

125
        // Test vertex at (50, 50) with perpendicular direction pointing down
126
        Point2D<float> vertex{50.0f, 50.0f};
1✔
127
        Point2D<float> perp_dir{0.0f, 1.0f};// Pointing down
1✔
128

129
        Point2D<float> center_point = calculate_fwhm_center(
1✔
130
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
131

132
        // Should find the center of the thick bright line at y=60 and return center point at (50, 60)
133
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
134
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
135
    }
15✔
136

137
    SECTION("Bright line detection with very thick line") {
14✔
138
        // Create a test image with a very thick bright horizontal line
139
        ImageSize image_size{100, 100};
1✔
140
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
141

142
        // Create a bright horizontal line at y=60 with thickness of 9 pixels
143
        for (int x = 0; x < 100; ++x) {
101✔
144
            // Line spans from y=56 to y=64 (9 pixels thick)
145
            for (int y = 56; y <= 64; ++y) {
1,000✔
146
                if (y >= 0 && y < 100) {
900✔
147
                    image_data[y * 100 + x] = 255;// Bright white line
900✔
148
                }
149
            }
150
        }
151

152
        // Test vertex at (50, 50) with perpendicular direction pointing down
153
        Point2D<float> vertex{50.0f, 50.0f};
1✔
154
        Point2D<float> perp_dir{0.0f, 1.0f};// Pointing down
1✔
155

156
        Point2D<float> center_point = calculate_fwhm_center(
1✔
157
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
158

159
        // Should find the center of the thick bright line at y=60 and return center point at (50, 60)
160
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
161
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
162
    }
15✔
163

164
    SECTION("Bright line detection with diagonal perpendicular") {
14✔
165
        // Create a test image with a bright diagonal line
166
        ImageSize image_size{100, 100};
1✔
167
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
168

169
        // Create a bright diagonal line
170
        for (int i = 0; i < 100; ++i) {
101✔
171
            int x = i;
100✔
172
            int y = i + 10;// Offset by 10 pixels
100✔
173
            if (x < 100 && y < 100) {
100✔
174
                image_data[y * 100 + x] = 255;// Bright white line
90✔
175
            }
176
        }
177

178
        // Test vertex at (50, 50) with perpendicular direction
179
        Point2D<float> vertex{50.0f, 50.0f};
1✔
180
        Point2D<float> perp_dir{-0.707f, 0.707f};// Diagonal perpendicular
1✔
181

182
        Point2D<float> center_point = calculate_fwhm_center(
1✔
183
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
184

185
        // Should find the bright line and return appropriate center point
186
        // This will be the nearest position of the diagonal line stretching from
187
        // (40, 50) to (50, 60) . This is (45, 55)
188
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(45.0f, 2.0f));
1✔
189
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(55.0f, 2.0f));
1✔
190
    }
15✔
191

192
    SECTION("Bright diagonal line detection with thickness") {
14✔
193
        // Create a test image with a bright diagonal line with thickness
194
        ImageSize image_size{100, 100};
1✔
195
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
196

197
        // Create a bright diagonal line from (10,10) to (90,90) with thickness
198
        for (int i = 10; i <= 90; ++i) {
82✔
199
            // Line spans from (i,i) with thickness of 3 pixels perpendicular to the line
200
            for (int offset = -1; offset <= 1; ++offset) {
324✔
201
                int x = i + offset;
243✔
202
                int y = i + offset;
243✔
203
                if (x >= 0 && x < 100 && y >= 0 && y < 100) {
243✔
204
                    image_data[y * 100 + x] = 255;// Bright white line
243✔
205
                }
206
            }
207
        }
208

209
        // Test vertex at (50, 50) with perpendicular direction
210
        Point2D<float> vertex{50.0f, 50.0f};
1✔
211
        Point2D<float> perp_dir{-0.707f, 0.707f};// Perpendicular to diagonal
1✔
212

213
        Point2D<float> center_point = calculate_fwhm_center(
1✔
214
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
215

216
        // Should find the diagonal line and return appropriate center point
217
        // The exact value depends on the FWHM calculation, but should be reasonable
218
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 5.0f));
1✔
219
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 5.0f));
1✔
220
    }
15✔
221

222
    SECTION("No bright features - should return zero") {
14✔
223
        // Create a completely black image
224
        ImageSize image_size{100, 100};
1✔
225
        std::vector<uint8_t> image_data(100 * 100, 0);// All black
3✔
226

227
        Point2D<float> vertex{50.0f, 50.0f};
1✔
228
        Point2D<float> perp_dir{0.0f, 1.0f};// Pointing down
1✔
229

230
        Point2D<float> center_point = calculate_fwhm_center(
1✔
231
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
232

233
        // Should return original vertex when no bright features are found
234
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
235
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
236
    }
15✔
237

238
    SECTION("Bright spot detection") {
14✔
239
        // Create a test image with a bright spot
240
        ImageSize image_size{100, 100};
1✔
241
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
242

243
        // Create a bright spot at (60, 50)
244
        for (int y = 45; y <= 55; ++y) {
12✔
245
            for (int x = 55; x <= 65; ++x) {
132✔
246
                if (x >= 0 && x < 100 && y >= 0 && y < 100) {
121✔
247
                    image_data[y * 100 + x] = 255;// Bright white spot
121✔
248
                }
249
            }
250
        }
251

252
        // Test vertex at (50, 50) with perpendicular direction pointing right
253
        Point2D<float> vertex{50.0f, 50.0f};
1✔
254
        Point2D<float> perp_dir{1.0f, 0.0f};// Pointing right
1✔
255

256
        Point2D<float> center_point = calculate_fwhm_center(
1✔
257
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
258

259
        // Should find the bright spot and return center point at (60, 50)
260
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
261
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
262
    }
15✔
263

264
    SECTION("Multiple bright lines - should find the closest") {
14✔
265
        // Create a test image with multiple bright horizontal lines
266
        ImageSize image_size{100, 100};
1✔
267
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
268

269
        // Create bright horizontal lines at y=30 and y=70
270
        for (int x = 0; x < 100; ++x) {
101✔
271
            image_data[30 * 100 + x] = 255;// Bright white line
100✔
272
            image_data[80 * 100 + x] = 255;// Bright white line
100✔
273
        }
274

275
        // Test vertex at (50, 50) with perpendicular direction pointing up
276
        Point2D<float> vertex{50.0f, 50.0f};
1✔
277
        Point2D<float> perp_dir{0.0f, -1.0f};// Pointing up
1✔
278

279
        Point2D<float> center_point = calculate_fwhm_center(
1✔
280
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
281

282
        // Should find the closer line at y=30 and return center point at (50, 30)
283
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
284
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(30.0f, 1.0f));
1✔
285
    }
15✔
286

287
    SECTION("Edge case - vertex at image boundary") {
14✔
288
        // Create a test image with a bright line
289
        ImageSize image_size{100, 100};
1✔
290
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
291

292
        // Create a bright horizontal line at y=50
293
        for (int x = 0; x < 100; ++x) {
101✔
294
            image_data[50 * 100 + x] = 255;// Bright white line
100✔
295
        }
296

297
        // Test vertex at (50, 0) with perpendicular direction pointing right
298
        Point2D<float> vertex{50.0f, 0.0f};
1✔
299
        Point2D<float> perp_dir{0.0f, 1.0f};// Pointing down
1✔
300

301
        Point2D<float> center_point = calculate_fwhm_center(
1✔
302
                vertex, perp_dir, 10, 102, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
303

304
        // Should handle boundary case gracefully
305
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
306
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
307
    }
15✔
308

309
    SECTION("Different perpendicular range values") {
14✔
310
        // Create a test image with a bright horizontal line
311
        ImageSize image_size{100, 100};
1✔
312
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
313

314
        // Create a bright horizontal line at y=60
315
        for (int x = 0; x < 100; ++x) {
101✔
316
            image_data[60 * 100 + x] = 255;// Bright white line
100✔
317
        }
318

319
        Point2D<float> vertex{50.0f, 50.0f};
1✔
320
        Point2D<float> perp_dir{0.0f, 1.0f};// Pointing down
1✔
321

322
        // Test with different perpendicular ranges
323
        Point2D<float> center_point1 = calculate_fwhm_center(
1✔
324
                vertex, perp_dir, 10, 20, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
325

326
        Point2D<float> center_point2 = calculate_fwhm_center(
1✔
327
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
328

329
        // Both should find the same line and return similar center points
330
        REQUIRE_THAT(center_point1.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
331
        REQUIRE_THAT(center_point1.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
332
        REQUIRE_THAT(center_point2.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
333
        REQUIRE_THAT(center_point2.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
334
    }
15✔
335

336
    SECTION("Different width values") {
14✔
337
        // Create a test image with a bright horizontal line
338
        ImageSize image_size{100, 100};
1✔
339
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
340

341
        // Create a bright horizontal line at y=60
342
        for (int x = 0; x < 100; ++x) {
101✔
343
            image_data[60 * 100 + x] = 255;// Bright white line
100✔
344
        }
345

346
        Point2D<float> vertex{50.0f, 50.0f};
1✔
347
        Point2D<float> perp_dir{0.0f, 1.0f};// Pointing down
1✔
348

349
        // Test with different width values
350
        Point2D<float> center_point1 = calculate_fwhm_center(
1✔
351
                vertex, perp_dir, 5, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
352

353
        Point2D<float> center_point2 = calculate_fwhm_center(
1✔
354
                vertex, perp_dir, 20, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
355

356
        // Both should find the same line and return similar center points
357
        REQUIRE_THAT(center_point1.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
358
        REQUIRE_THAT(center_point1.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
359
        REQUIRE_THAT(center_point2.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
360
        REQUIRE_THAT(center_point2.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
361
    }
15✔
362
}
14✔
363

364
TEST_CASE("Data Transform: Line Alignment - Perpendicular direction calculation - Core functionality", "[line][alignment][perpendicular][transform]") {
7✔
365

366
    SECTION("Horizontal line - perpendicular should be vertical") {
7✔
367
        // Create a horizontal line from (0,0) to (10,0)
368
        Line2D horizontal_line;
1✔
369
        horizontal_line.push_back(Point2D<float>{0.0f, 0.0f});
1✔
370
        horizontal_line.push_back(Point2D<float>{10.0f, 0.0f});
1✔
371

372
        // Test first vertex (should use direction to next vertex)
373
        Point2D<float> perp_dir_first = calculate_perpendicular_direction(horizontal_line, 0);
1✔
374
        REQUIRE_THAT(perp_dir_first.x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
375
        REQUIRE_THAT(perp_dir_first.y, Catch::Matchers::WithinAbs(1.0f, 0.001f));// Pointing down
1✔
376

377
        // Test last vertex (should use direction from previous vertex)
378
        Point2D<float> perp_dir_last = calculate_perpendicular_direction(horizontal_line, 1);
1✔
379
        REQUIRE_THAT(perp_dir_last.x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
380
        REQUIRE_THAT(perp_dir_last.y, Catch::Matchers::WithinAbs(1.0f, 0.001f));// Pointing down
1✔
381
    }
8✔
382

383
    SECTION("Vertical line - perpendicular should be horizontal") {
7✔
384
        // Create a vertical line from (0,0) to (0,10)
385
        Line2D vertical_line;
1✔
386
        vertical_line.push_back(Point2D<float>{0.0f, 0.0f});
1✔
387
        vertical_line.push_back(Point2D<float>{0.0f, 10.0f});
1✔
388

389
        // Test first vertex
390
        Point2D<float> perp_dir_first = calculate_perpendicular_direction(vertical_line, 0);
1✔
391
        REQUIRE_THAT(perp_dir_first.x, Catch::Matchers::WithinAbs(-1.0f, 0.001f));// Pointing left
1✔
392
        REQUIRE_THAT(perp_dir_first.y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
393

394
        // Test last vertex
395
        Point2D<float> perp_dir_last = calculate_perpendicular_direction(vertical_line, 1);
1✔
396
        REQUIRE_THAT(perp_dir_last.x, Catch::Matchers::WithinAbs(-1.0f, 0.001f));// Pointing left
1✔
397
        REQUIRE_THAT(perp_dir_last.y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
398
    }
8✔
399

400
    SECTION("Diagonal line - perpendicular should be perpendicular") {
7✔
401
        // Create a diagonal line from (0,0) to (10,10)
402
        Line2D diagonal_line;
1✔
403
        diagonal_line.push_back(Point2D<float>{0.0f, 0.0f});
1✔
404
        diagonal_line.push_back(Point2D<float>{10.0f, 10.0f});
1✔
405

406
        // Test first vertex
407
        Point2D<float> perp_dir_first = calculate_perpendicular_direction(diagonal_line, 0);
1✔
408
        // Perpendicular to (10,10) - (0,0) = (10,10) should be (-10,10) normalized
409
        REQUIRE_THAT(perp_dir_first.x, Catch::Matchers::WithinAbs(-0.707f, 0.001f));// -1/sqrt(2)
1✔
410
        REQUIRE_THAT(perp_dir_first.y, Catch::Matchers::WithinAbs(0.707f, 0.001f)); // 1/sqrt(2)
1✔
411

412
        // Test last vertex
413
        Point2D<float> perp_dir_last = calculate_perpendicular_direction(diagonal_line, 1);
1✔
414
        REQUIRE_THAT(perp_dir_last.x, Catch::Matchers::WithinAbs(-0.707f, 0.001f));
1✔
415
        REQUIRE_THAT(perp_dir_last.y, Catch::Matchers::WithinAbs(0.707f, 0.001f));
1✔
416
    }
8✔
417

418
    SECTION("Multi-segment line - middle vertices average perpendiculars") {
7✔
419
        // Create a line with three segments: horizontal, diagonal, vertical
420
        Line2D multi_line;
1✔
421
        multi_line.push_back(Point2D<float>{0.0f, 0.0f});  // Start
1✔
422
        multi_line.push_back(Point2D<float>{10.0f, 0.0f}); // Horizontal segment
1✔
423
        multi_line.push_back(Point2D<float>{20.0f, 10.0f});// Diagonal segment
1✔
424
        multi_line.push_back(Point2D<float>{20.0f, 20.0f});// Vertical segment
1✔
425

426
        // Test middle vertex (index 1) - should average perpendiculars from adjacent segments
427
        Point2D<float> perp_dir_middle = calculate_perpendicular_direction(multi_line, 1);
1✔
428

429
        // First segment: (10,0) - (0,0) = (10,0), perpendicular = (0,1)
430
        // Second segment: (20,10) - (10,0) = (10,10), perpendicular = (-10,10)/sqrt(200) = (-0.707, 0.707)
431
        // Average: (0 + (-0.707))/2 = -0.3535, (1 + 0.707)/2 = 0.8535
432
        // Normalized: approximately (-0.383, 0.924)
433
        REQUIRE_THAT(perp_dir_middle.x, Catch::Matchers::WithinAbs(-0.383f, 0.1f));
1✔
434
        REQUIRE_THAT(perp_dir_middle.y, Catch::Matchers::WithinAbs(0.924f, 0.1f));
1✔
435
    }
8✔
436

437
    SECTION("Line with fewer than 2 points - should return zero vector") {
7✔
438
        // Create a line with only 1 point
439
        Line2D short_line;
1✔
440
        short_line.push_back(Point2D<float>{0.0f, 0.0f});
1✔
441

442
        // Test with 1 point (invalid for perpendicular calculation)
443
        Point2D<float> perp_dir = calculate_perpendicular_direction(short_line, 0);
1✔
444
        REQUIRE_THAT(perp_dir.x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
445
        REQUIRE_THAT(perp_dir.y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
446
    }
8✔
447

448
    SECTION("Line with zero-length segments - should return zero vector") {
7✔
449
        // Create a line with zero-length segments
450
        Line2D zero_line;
1✔
451
        zero_line.push_back(Point2D<float>{5.0f, 5.0f});
1✔
452
        zero_line.push_back(Point2D<float>{5.0f, 5.0f});// Same point
1✔
453
        zero_line.push_back(Point2D<float>{5.0f, 5.0f});// Same point
1✔
454

455
        // Test middle vertex
456
        Point2D<float> perp_dir = calculate_perpendicular_direction(zero_line, 1);
1✔
457
        REQUIRE_THAT(perp_dir.x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
458
        REQUIRE_THAT(perp_dir.y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
459
    }
8✔
460

461
    SECTION("Normalized perpendicular vectors") {
7✔
462
        // Test that all perpendicular vectors are normalized (unit length)
463
        Line2D test_line;
1✔
464
        test_line.push_back(Point2D<float>{0.0f, 0.0f});
1✔
465
        test_line.push_back(Point2D<float>{3.0f, 4.0f});// 3-4-5 triangle
1✔
466
        test_line.push_back(Point2D<float>{6.0f, 8.0f});
1✔
467

468
        // Test first vertex
469
        Point2D<float> perp_dir = calculate_perpendicular_direction(test_line, 0);
1✔
470
        float length = std::sqrt(perp_dir.x * perp_dir.x + perp_dir.y * perp_dir.y);
1✔
471
        REQUIRE_THAT(length, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
472

473
        // Test middle vertex
474
        Point2D<float> perp_dir_middle = calculate_perpendicular_direction(test_line, 1);
1✔
475
        float length_middle = std::sqrt(perp_dir_middle.x * perp_dir_middle.x + perp_dir_middle.y * perp_dir_middle.y);
1✔
476
        REQUIRE_THAT(length_middle, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
477

478
        // Test last vertex
479
        Point2D<float> perp_dir_last = calculate_perpendicular_direction(test_line, 2);
1✔
480
        float length_last = std::sqrt(perp_dir_last.x * perp_dir_last.x + perp_dir_last.y * perp_dir_last.y);
1✔
481
        REQUIRE_THAT(length_last, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
482
    }
8✔
483
}
7✔
484

485
TEST_CASE("Data Transform: Line Alignment - FWHM center calculation - Edge cases and error handling", "[line][alignment][fwhm][edge]") {
4✔
486

487
    SECTION("Zero width parameter") {
4✔
488
        ImageSize image_size{100, 100};
1✔
489
        std::vector<uint8_t> image_data(100 * 100, 0);
3✔
490

491
        Point2D<float> vertex{50.0f, 50.0f};
1✔
492
        Point2D<float> perp_dir{0.0f, 1.0f};
1✔
493

494
        Point2D<float> center_point = calculate_fwhm_center(
1✔
495
                vertex, perp_dir, 0, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
496

497
        // Should return original vertex for invalid width
498
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
499
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
500
    }
5✔
501

502
    SECTION("Zero perpendicular direction") {
4✔
503
        ImageSize image_size{100, 100};
1✔
504
        std::vector<uint8_t> image_data(100 * 100, 0);
3✔
505

506
        Point2D<float> vertex{50.0f, 50.0f};
1✔
507
        Point2D<float> perp_dir{0.0f, 0.0f};// Zero direction
1✔
508

509
        Point2D<float> center_point = calculate_fwhm_center(
1✔
510
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
511

512
        // Should handle zero direction gracefully
513
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
514
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
515
    }
5✔
516

517
    SECTION("Empty image data") {
4✔
518
        ImageSize image_size{100, 100};
1✔
519
        std::vector<uint8_t> image_data;// Empty
1✔
520

521
        Point2D<float> vertex{50.0f, 50.0f};
1✔
522
        Point2D<float> perp_dir{0.0f, 1.0f};
1✔
523

524
        Point2D<float> center_point = calculate_fwhm_center(
1✔
525
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
526

527
        // Should handle empty data gracefully
528
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
529
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
530
    }
5✔
531

532
    SECTION("Vertex outside image bounds") {
4✔
533
        ImageSize image_size{100, 100};
1✔
534
        std::vector<uint8_t> image_data(100 * 100, 0);
3✔
535

536
        Point2D<float> vertex{150.0f, 150.0f};// Outside bounds
1✔
537
        Point2D<float> perp_dir{0.0f, 1.0f};
1✔
538

539
        Point2D<float> center_point = calculate_fwhm_center(
1✔
540
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
541

542
        // Should handle out-of-bounds vertex gracefully
543
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(150.0f, 0.001f));
1✔
544
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(150.0f, 0.001f));
1✔
545
    }
5✔
546
}
4✔
547

548
#if LINE_ALIGNMENT_DEBUG_MODE
549
TEST_CASE("Data Transform: Line Alignment - FWHM profile extents calculation - Debug mode", "[line][alignment][fwhm][debug]") {
550

551
    SECTION("Debug mode profile extents for bright line") {
552
        // Create a test image with a bright horizontal line
553
        ImageSize image_size{100, 100};
554
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
555

556
        // Create a bright horizontal line at y=60
557
        for (int x = 0; x < 100; ++x) {
558
            image_data[60 * 100 + x] = 255;// Bright white line
559
        }
560

561
        // Test vertex at (50, 50) with perpendicular direction pointing down
562
        Point2D<float> vertex{50.0f, 50.0f};
563
        Point2D<float> perp_dir{0.0f, 1.0f};// Pointing down
564

565
        Line2D debug_line = calculate_fwhm_profile_extents(
566
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
567

568
        // Should return a line with 3 points: [left_extent, max_point, right_extent]
569
        REQUIRE(debug_line.size() == 3);
570

571
        // First point should be left extent (around y=55-65)
572
        REQUIRE_THAT(debug_line[0].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
573
        REQUIRE_THAT(debug_line[0].y, Catch::Matchers::WithinAbs(60.0f, 5.0f));
574

575
        // Second point should be max point (around y=60)
576
        REQUIRE_THAT(debug_line[1].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
577
        REQUIRE_THAT(debug_line[1].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
578

579
        // Third point should be right extent (around y=55-65)
580
        REQUIRE_THAT(debug_line[2].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
581
        REQUIRE_THAT(debug_line[2].y, Catch::Matchers::WithinAbs(60.0f, 5.0f));
582
    }
583

584
    SECTION("Debug mode with multiple maximum values") {
585
        // Create a test image with a bright horizontal line that has multiple maximum values
586
        ImageSize image_size{100, 100};
587
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
588

589
        // Create a bright horizontal line at y=60 with some thickness
590
        for (int x = 0; x < 100; ++x) {
591
            // Create a line with multiple maximum values at different positions
592
            for (int y = 58; y <= 62; ++y) {
593
                if (y >= 0 && y < 100) {
594
                    image_data[y * 100 + x] = 255;// Bright white line
595
                }
596
            }
597
        }
598

599
        // Test vertex at (50, 50) with perpendicular direction pointing down
600
        Point2D<float> vertex{50.0f, 50.0f};
601
        Point2D<float> perp_dir{0.0f, 1.0f};// Pointing down
602

603
        Line2D debug_line = calculate_fwhm_profile_extents(
604
                vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
605

606
        // Should return a line with 3 points: [left_extent, max_point, right_extent]
607
        REQUIRE(debug_line.size() == 3);
608

609
        // First point should be left extent
610
        REQUIRE_THAT(debug_line[0].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
611
        REQUIRE_THAT(debug_line[0].y, Catch::Matchers::WithinAbs(60.0f, 5.0f));
612

613
        // Second point should be max point (average of multiple max positions)
614
        REQUIRE_THAT(debug_line[1].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
615
        REQUIRE_THAT(debug_line[1].y, Catch::Matchers::WithinAbs(60.0f, 2.0f));// Should be around center of thick line
616

617
        // Third point should be right extent
618
        REQUIRE_THAT(debug_line[2].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
619
        REQUIRE_THAT(debug_line[2].y, Catch::Matchers::WithinAbs(60.0f, 5.0f));
620
    }
621
}
622
#endif
623

624
#include "DataManager.hpp"
625
#include "IO/LoaderRegistry.hpp"
626
#include "Lines/Line_Data.hpp"
627
#include "Media/Media_Data.hpp"
628
#include "transforms/TransformPipeline.hpp"
629
#include "transforms/TransformRegistry.hpp"
630

631
#include <filesystem>
632
#include <fstream>
633
#include <iostream>
634

635
TEST_CASE("Data Transform: Line Alignment - JSON pipeline", "[transforms][line_alignment][json]") {
1✔
636
    // Create DataManager and populate it with LineData and MediaData in code
637
    DataManager dm;
1✔
638

639
    // Create a TimeFrame for our data
640
    auto time_frame = std::make_shared<TimeFrame>();
1✔
641
    dm.setTime(TimeKey("default"), time_frame);
1✔
642

643
    // Create test line data - a simple horizontal line
644
    Line2D test_line;
1✔
645
    test_line.push_back(Point2D<float>{10.0f, 50.0f});
1✔
646
    test_line.push_back(Point2D<float>{50.0f, 50.0f});
1✔
647
    test_line.push_back(Point2D<float>{90.0f, 50.0f});
1✔
648

649
    auto test_line_data = std::make_shared<LineData>();
1✔
650
    test_line_data->setTimeFrame(time_frame);
1✔
651
    test_line_data->addAtTime(TimeFrameIndex(0), test_line);
1✔
652

653
    // Store the line data in DataManager with a known key
654
    dm.setData("test_line", test_line_data, TimeKey("default"));
3✔
655

656
    // Create test media data with a bright horizontal line at y=60
657
    auto media_data = std::make_shared<MockMediaData>();
1✔
658
    media_data->setTimeFrame(time_frame);
1✔
659

660
    // Create a test image with a bright horizontal line at y=60
661
    ImageSize image_size{100, 100};
1✔
662
    std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
663

664
    // Create a bright horizontal line at y=60
665
    for (int x = 0; x < 100; ++x) {
101✔
666
        image_data[60 * 100 + x] = 255;// Bright white line
100✔
667
    }
668

669
    // Add the image to media data
670
    media_data->addImage(image_data, image_size);
1✔
671

672
    // Store the media data in DataManager with a known key
673
    dm.setData("test_media", media_data, TimeKey("default"));
3✔
674

675
    // Create JSON configuration for transformation pipeline using unified format
676
    char const * json_config =
1✔
677
            "[\n"
678
            "{\n"
679
            "    \"transformations\": {\n"
680
            "        \"metadata\": {\n"
681
            "            \"name\": \"Line Alignment Pipeline\",\n"
682
            "            \"description\": \"Test line alignment to bright features in media\",\n"
683
            "            \"version\": \"1.0\"\n"
684
            "        },\n"
685
            "        \"steps\": [\n"
686
            "            {\n"
687
            "                \"step_id\": \"1\",\n"
688
            "                \"transform_name\": \"Line Alignment to Bright Features\",\n"
689
            "                \"phase\": \"analysis\",\n"
690
            "                \"input_key\": \"test_line\",\n"
691
            "                \"output_key\": \"aligned_line\",\n"
692
            "                \"parameters\": {\n"
693
            "                    \"media_data\": \"test_media\",\n"
694
            "                    \"width\": 20,\n"
695
            "                    \"perpendicular_range\": 50,\n"
696
            "                    \"use_processed_data\": true,\n"
697
            "                    \"approach\": \"PEAK_WIDTH_HALF_MAX\",\n"
698
            "                    \"output_mode\": \"ALIGNED_VERTICES\"\n"
699
            "                }\n"
700
            "            }\n"
701
            "        ]\n"
702
            "    }\n"
703
            "}\n"
704
            "]";
705

706
    // Create temporary directory and write JSON config to file
707
    std::filesystem::path test_dir = std::filesystem::temp_directory_path() / "line_alignment_pipeline_test";
1✔
708
    std::filesystem::create_directories(test_dir);
1✔
709

710
    std::filesystem::path json_filepath = test_dir / "pipeline_config.json";
1✔
711
    {
712
        std::ofstream json_file(json_filepath);
1✔
713
        REQUIRE(json_file.is_open());
1✔
714
        json_file << json_config;
1✔
715
        json_file.close();
1✔
716
    }
1✔
717

718
    // Execute the transformation pipeline using load_data_from_json_config
719
    auto data_info_list = load_data_from_json_config(&dm, json_filepath.string());
1✔
720

721
    // Verify the transformation was executed and results are available
722
    auto result_line = dm.getData<LineData>("aligned_line");
3✔
723
    REQUIRE(result_line != nullptr);
1✔
724

725
    // Verify the line alignment results
726
    // The original line was at y=50, but there's a bright line at y=60
727
    // The aligned line should have vertices moved to y=60
728
    auto aligned_lines = result_line->getAtTime(TimeFrameIndex(0));
1✔
729
    REQUIRE(aligned_lines.size() == 1);
1✔
730
    auto aligned_vertices = aligned_lines[0];
1✔
731
    REQUIRE(aligned_vertices.size() == 3);
1✔
732

733
    // Check that vertices were aligned to the bright line at y=60
734
    REQUIRE_THAT(aligned_vertices[0].x, Catch::Matchers::WithinAbs(10.0f, 1.0f));
1✔
735
    REQUIRE_THAT(aligned_vertices[0].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
736
    REQUIRE_THAT(aligned_vertices[1].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
737
    REQUIRE_THAT(aligned_vertices[1].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
738
    REQUIRE_THAT(aligned_vertices[2].x, Catch::Matchers::WithinAbs(90.0f, 1.0f));
1✔
739
    REQUIRE_THAT(aligned_vertices[2].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
740

741
    // Test another pipeline with different parameters (different width and range)
742
    char const * json_config_different_params =
1✔
743
            "[\n"
744
            "{\n"
745
            "    \"transformations\": {\n"
746
            "        \"metadata\": {\n"
747
            "            \"name\": \"Line Alignment with Different Parameters\",\n"
748
            "            \"description\": \"Test line alignment with different width and range\",\n"
749
            "            \"version\": \"1.0\"\n"
750
            "        },\n"
751
            "        \"steps\": [\n"
752
            "            {\n"
753
            "                \"step_id\": \"1\",\n"
754
            "                \"transform_name\": \"Line Alignment to Bright Features\",\n"
755
            "                \"phase\": \"analysis\",\n"
756
            "                \"input_key\": \"test_line\",\n"
757
            "                \"output_key\": \"aligned_line_different_params\",\n"
758
            "                \"parameters\": {\n"
759
            "                    \"media_data\": \"test_media\",\n"
760
            "                    \"width\": 10,\n"
761
            "                    \"perpendicular_range\": 30,\n"
762
            "                    \"use_processed_data\": true,\n"
763
            "                    \"approach\": \"PEAK_WIDTH_HALF_MAX\",\n"
764
            "                    \"output_mode\": \"ALIGNED_VERTICES\"\n"
765
            "                }\n"
766
            "            }\n"
767
            "        ]\n"
768
            "    }\n"
769
            "}\n"
770
            "]";
771

772
    std::filesystem::path json_filepath_different_params = test_dir / "pipeline_config_different_params.json";
1✔
773
    {
774
        std::ofstream json_file(json_filepath_different_params);
1✔
775
        REQUIRE(json_file.is_open());
1✔
776
        json_file << json_config_different_params;
1✔
777
        json_file.close();
1✔
778
    }
1✔
779

780
    // Execute the different parameters pipeline
781
    auto data_info_list_different_params = load_data_from_json_config(&dm, json_filepath_different_params.string());
1✔
782

783
    // Verify the different parameters results
784
    auto result_line_different_params = dm.getData<LineData>("aligned_line_different_params");
3✔
785
    REQUIRE(result_line_different_params != nullptr);
1✔
786

787
    auto aligned_lines_different_params = result_line_different_params->getAtTime(TimeFrameIndex(0));
1✔
788
    REQUIRE(aligned_lines_different_params.size() == 1);
1✔
789
    auto aligned_vertices_different_params = aligned_lines_different_params[0];
1✔
790
    REQUIRE(aligned_vertices_different_params.size() == 3);
1✔
791

792
    // Should still align to the bright line at y=60
793
    REQUIRE_THAT(aligned_vertices_different_params[0].x, Catch::Matchers::WithinAbs(10.0f, 1.0f));
1✔
794
    REQUIRE_THAT(aligned_vertices_different_params[0].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
795
    REQUIRE_THAT(aligned_vertices_different_params[1].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
796
    REQUIRE_THAT(aligned_vertices_different_params[1].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
797
    REQUIRE_THAT(aligned_vertices_different_params[2].x, Catch::Matchers::WithinAbs(90.0f, 1.0f));
1✔
798
    REQUIRE_THAT(aligned_vertices_different_params[2].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
799

800
    // Test FWHM profile extents output mode
801
    char const * json_config_fwhm_extents =
1✔
802
            "[\n"
803
            "{\n"
804
            "    \"transformations\": {\n"
805
            "        \"metadata\": {\n"
806
            "            \"name\": \"Line Alignment FWHM Extents\",\n"
807
            "            \"description\": \"Test line alignment with FWHM profile extents output\",\n"
808
            "            \"version\": \"1.0\"\n"
809
            "        },\n"
810
            "        \"steps\": [\n"
811
            "            {\n"
812
            "                \"step_id\": \"1\",\n"
813
            "                \"transform_name\": \"Line Alignment to Bright Features\",\n"
814
            "                \"phase\": \"analysis\",\n"
815
            "                \"input_key\": \"test_line\",\n"
816
            "                \"output_key\": \"fwhm_extents_line\",\n"
817
            "                \"parameters\": {\n"
818
            "                    \"media_data\": \"test_media\",\n"
819
            "                    \"width\": 20,\n"
820
            "                    \"perpendicular_range\": 50,\n"
821
            "                    \"use_processed_data\": true,\n"
822
            "                    \"approach\": \"PEAK_WIDTH_HALF_MAX\",\n"
823
            "                    \"output_mode\": \"FWHM_PROFILE_EXTENTS\"\n"
824
            "                }\n"
825
            "            }\n"
826
            "        ]\n"
827
            "    }\n"
828
            "}\n"
829
            "]";
830

831
    std::filesystem::path json_filepath_fwhm_extents = test_dir / "pipeline_config_fwhm_extents.json";
1✔
832
    {
833
        std::ofstream json_file(json_filepath_fwhm_extents);
1✔
834
        REQUIRE(json_file.is_open());
1✔
835
        json_file << json_config_fwhm_extents;
1✔
836
        json_file.close();
1✔
837
    }
1✔
838

839
    // Execute the FWHM extents pipeline
840
    auto data_info_list_fwhm_extents = load_data_from_json_config(&dm, json_filepath_fwhm_extents.string());
1✔
841

842
    // Verify the FWHM extents results
843
    auto result_line_fwhm_extents = dm.getData<LineData>("fwhm_extents_line");
3✔
844
    REQUIRE(result_line_fwhm_extents != nullptr);
1✔
845

846
    auto fwhm_extents_lines = result_line_fwhm_extents->getAtTime(TimeFrameIndex(0));
1✔
847
    REQUIRE(fwhm_extents_lines.size() == 3);// One line per vertex
1✔
848
    auto fwhm_extents_vertices = fwhm_extents_lines[0];
1✔
849
    // Should have 3 points per vertex: [left_extent, max_point, right_extent]
850
    // For 3 vertices, this should be 9 points total
851
    REQUIRE(fwhm_extents_vertices.size() == 3);
1✔
852

853
    // Cleanup
854
    try {
855
        std::filesystem::remove_all(test_dir);
1✔
856
    } catch (std::exception const & e) {
×
857
        std::cerr << "Warning: Cleanup failed: " << e.what() << std::endl;
×
858
    }
×
859
}
2✔
860

861
TEST_CASE("Data Transform: Line Alignment - Line alignment with 8-bit and 32-bit media data", "[line][alignment][media][transform]") {
3✔
862

863
    SECTION("8-bit media data alignment") {
3✔
864
        // Create a test image with a bright horizontal line
865
        ImageSize image_size{100, 100};
1✔
866
        std::vector<uint8_t> image_data(100 * 100, 0);// All black initially
3✔
867

868
        // Create a bright horizontal line at y=60
869
        for (int x = 0; x < 100; ++x) {
101✔
870
            image_data[60 * 100 + x] = 255;// Bright white line
100✔
871
        }
872

873
        // Create mock media data with 8-bit data
874
        auto media_data = std::make_shared<MockMediaData>(MediaData::BitDepth::Bit8);
1✔
875
        media_data->addImage8(image_data, image_size);
1✔
876

877
        // Create test line data
878
        auto line_data = std::make_shared<LineData>();
1✔
879
        line_data->setImageSize(image_size);
1✔
880

881
        // Create a test line with vertices that should align to the bright line
882
        Line2D test_line;
1✔
883
        test_line.push_back(Point2D<float>{30.0f, 50.0f});// Should move to y=60
1✔
884
        test_line.push_back(Point2D<float>{50.0f, 55.0f});// Should move to y=60
1✔
885
        test_line.push_back(Point2D<float>{70.0f, 45.0f});// Should move to y=60
1✔
886

887
        line_data->addAtTime(TimeFrameIndex(0), test_line, false);
1✔
888

889
        // Test the line alignment function
890
        auto aligned_line_data = line_alignment(
1✔
891
                line_data.get(),
1✔
892
                media_data.get(),
1✔
893
                20,   // width
894
                50,   // perpendicular_range
895
                false,// use_processed_data
896
                FWHMApproach::PEAK_WIDTH_HALF_MAX,
897
                LineAlignmentOutputMode::ALIGNED_VERTICES);
1✔
898

899
        REQUIRE(aligned_line_data != nullptr);
1✔
900

901
        // Check that the lines were aligned
902
        auto aligned_lines = aligned_line_data->getAtTime(TimeFrameIndex(0));
1✔
903
        REQUIRE(aligned_lines.size() == 1);
1✔
904

905
        auto const & aligned_line = aligned_lines[0];
1✔
906
        REQUIRE(aligned_line.size() == 3);
1✔
907

908
        // All vertices should be aligned to y=60 (the bright line)
909
        for (auto const & vertex: aligned_line) {
4✔
910
            REQUIRE_THAT(vertex.y, Catch::Matchers::WithinAbs(60.0f, 2.0f));
3✔
911
        }
912
    }
4✔
913

914
    SECTION("32-bit media data alignment") {
3✔
915
        // Create a test image with a bright horizontal line using 32-bit float data
916
        ImageSize image_size{100, 100};
1✔
917
        std::vector<float> image_data(100 * 100, 0.0f);// All black initially
3✔
918

919
        // Create a bright horizontal line at y=60 with float intensity
920
        for (int x = 0; x < 100; ++x) {
101✔
921
            image_data[60 * 100 + x] = 1.0f;// Bright white line (normalized to 0-1)
100✔
922
        }
923

924
        // Create mock media data with 32-bit data
925
        auto media_data = std::make_shared<MockMediaData>(MediaData::BitDepth::Bit32);
1✔
926
        media_data->addImage32(image_data, image_size);
1✔
927

928
        // Create test line data
929
        auto line_data = std::make_shared<LineData>();
1✔
930
        line_data->setImageSize(image_size);
1✔
931

932
        // Create a test line with vertices that should align to the bright line
933
        Line2D test_line;
1✔
934
        test_line.push_back(Point2D<float>{30.0f, 50.0f});// Should move to y=60
1✔
935
        test_line.push_back(Point2D<float>{50.0f, 55.0f});// Should move to y=60
1✔
936
        test_line.push_back(Point2D<float>{70.0f, 45.0f});// Should move to y=60
1✔
937

938
        line_data->addAtTime(TimeFrameIndex(0), test_line, false);
1✔
939

940
        // Test the line alignment function
941
        auto aligned_line_data = line_alignment(
1✔
942
                line_data.get(),
1✔
943
                media_data.get(),
1✔
944
                20,   // width
945
                50,   // perpendicular_range
946
                false,// use_processed_data
947
                FWHMApproach::PEAK_WIDTH_HALF_MAX,
948
                LineAlignmentOutputMode::ALIGNED_VERTICES);
1✔
949

950
        REQUIRE(aligned_line_data != nullptr);
1✔
951

952
        // Check that the lines were aligned
953
        auto aligned_lines = aligned_line_data->getAtTime(TimeFrameIndex(0));
1✔
954
        REQUIRE(aligned_lines.size() == 1);
1✔
955

956
        auto const & aligned_line = aligned_lines[0];
1✔
957
        REQUIRE(aligned_line.size() == 3);
1✔
958

959
        // All vertices should be aligned to y=60 (the bright line)
960
        for (auto const & vertex: aligned_line) {
4✔
961
            REQUIRE_THAT(vertex.y, Catch::Matchers::WithinAbs(60.0f, 2.0f));
3✔
962
        }
963
    }
4✔
964

965
    SECTION("Compare 8-bit vs 32-bit alignment results") {
3✔
966
        ImageSize image_size{100, 100};
1✔
967

968
        // Create identical bright horizontal lines in both formats
969
        std::vector<uint8_t> image_data_8bit(100 * 100, 0);
3✔
970
        std::vector<float> image_data_32bit(100 * 100, 0.0f);
3✔
971

972
        for (int x = 0; x < 100; ++x) {
101✔
973
            image_data_8bit[60 * 100 + x] = 255;
100✔
974
            image_data_32bit[60 * 100 + x] = 1.0f;
100✔
975
        }
976

977
        // Create mock media data for both bit depths
978
        auto media_data_8bit = std::make_shared<MockMediaData>(MediaData::BitDepth::Bit8);
1✔
979
        media_data_8bit->addImage8(image_data_8bit, image_size);
1✔
980

981
        auto media_data_32bit = std::make_shared<MockMediaData>(MediaData::BitDepth::Bit32);
1✔
982
        media_data_32bit->addImage32(image_data_32bit, image_size);
1✔
983

984
        // Create identical test line data for both tests
985
        auto line_data = std::make_shared<LineData>();
1✔
986
        line_data->setImageSize(image_size);
1✔
987

988
        Line2D test_line;
1✔
989
        test_line.push_back(Point2D<float>{30.0f, 50.0f});
1✔
990
        test_line.push_back(Point2D<float>{50.0f, 55.0f});
1✔
991
        test_line.push_back(Point2D<float>{70.0f, 45.0f});
1✔
992

993
        line_data->addAtTime(TimeFrameIndex(0), test_line, false);
1✔
994

995
        // Test alignment with 8-bit data
996
        auto aligned_line_data_8bit = line_alignment(
1✔
997
                line_data.get(),
1✔
998
                media_data_8bit.get(),
1✔
999
                20, 50, false,
1000
                FWHMApproach::PEAK_WIDTH_HALF_MAX,
1001
                LineAlignmentOutputMode::ALIGNED_VERTICES);
1✔
1002

1003
        // Test alignment with 32-bit data
1004
        auto aligned_line_data_32bit = line_alignment(
1✔
1005
                line_data.get(),
1✔
1006
                media_data_32bit.get(),
1✔
1007
                20, 50, false,
1008
                FWHMApproach::PEAK_WIDTH_HALF_MAX,
1009
                LineAlignmentOutputMode::ALIGNED_VERTICES);
1✔
1010

1011
        REQUIRE(aligned_line_data_8bit != nullptr);
1✔
1012
        REQUIRE(aligned_line_data_32bit != nullptr);
1✔
1013

1014
        // Get aligned lines from both results
1015
        auto aligned_lines_8bit = aligned_line_data_8bit->getAtTime(TimeFrameIndex(0));
1✔
1016
        auto aligned_lines_32bit = aligned_line_data_32bit->getAtTime(TimeFrameIndex(0));
1✔
1017

1018
        REQUIRE(aligned_lines_8bit.size() == 1);
1✔
1019
        REQUIRE(aligned_lines_32bit.size() == 1);
1✔
1020

1021
        auto const & aligned_line_8bit = aligned_lines_8bit[0];
1✔
1022
        auto const & aligned_line_32bit = aligned_lines_32bit[0];
1✔
1023

1024
        REQUIRE(aligned_line_8bit.size() == 3);
1✔
1025
        REQUIRE(aligned_line_32bit.size() == 3);
1✔
1026

1027
        // Results should be very similar between 8-bit and 32-bit
1028
        for (size_t i = 0; i < 3; ++i) {
4✔
1029
            REQUIRE_THAT(aligned_line_8bit[i].x, Catch::Matchers::WithinAbs(aligned_line_32bit[i].x, 1.0f));
3✔
1030
            REQUIRE_THAT(aligned_line_8bit[i].y, Catch::Matchers::WithinAbs(aligned_line_32bit[i].y, 1.0f));
3✔
1031
        }
1032
    }
4✔
1033
}
3✔
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