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

paulmthompson / WhiskerToolbox / 17410599011

02 Sep 2025 04:52PM UTC coverage: 71.464% (+0.07%) from 71.394%
17410599011

push

github

paulmthompson
single channel colormaps added

0 of 155 new or added lines in 1 file covered. (0.0%)

75 existing lines in 4 files now uncovered.

31905 of 44645 relevant lines covered (71.46%)

1385.34 hits per line

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

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

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

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

17
// Mock MediaData subclass for testing
18
class MockMediaData : public MediaData {
19
public:
20
    MockMediaData(BitDepth bit_depth = BitDepth::Bit8) {
5✔
21
        setBitDepth(bit_depth);
5✔
22
    }
5✔
23
    
UNCOV
24
    MediaType getMediaType() const override { 
×
UNCOV
25
        return MediaType::Images; 
×
26
    }
27
    
28
    /**
29
     * @brief Add an 8-bit image to the mock media data
30
     * 
31
     * @param image_data The image data as uint8_t vector
32
     * @param image_size The image dimensions
33
     */
34
    void addImage8(std::vector<uint8_t> const& image_data, ImageSize const& image_size) {
3✔
35
        setBitDepth(BitDepth::Bit8);
3✔
36
        _stored_image_8bit = image_data;
3✔
37
        updateWidth(image_size.width);
3✔
38
        updateHeight(image_size.height);
3✔
39
        setTotalFrameCount(1); // Simple mock - only one frame
3✔
40
    }
3✔
41
    
42
    /**
43
     * @brief Add a 32-bit float image to the mock media data
44
     * 
45
     * @param image_data The image data as float vector
46
     * @param image_size The image dimensions
47
     */
48
    void addImage32(std::vector<float> const& image_data, ImageSize const& image_size) {
2✔
49
        setBitDepth(BitDepth::Bit32);
2✔
50
        _stored_image_32bit = image_data;
2✔
51
        updateWidth(image_size.width);
2✔
52
        updateHeight(image_size.height);
2✔
53
        setTotalFrameCount(1); // Simple mock - only one frame
2✔
54
    }
2✔
55
    
56
    /**
57
     * @brief Add an image based on the current bit depth
58
     */
59
    template<typename T>
60
    void addImage(std::vector<T> const& image_data, ImageSize const& image_size) {
1✔
61
        if constexpr (std::is_same_v<T, uint8_t>) {
62
            addImage8(image_data, image_size);
1✔
63
        } else if constexpr (std::is_same_v<T, float>) {
64
            addImage32(image_data, image_size);
65
        }
66
    }
1✔
67
    
68
protected:
UNCOV
69
    void doLoadMedia(std::string const& name) override {
×
70
        // No-op for mock data
71
        static_cast<void>(name);
UNCOV
72
    }
×
73
    
74
    void doLoadFrame(int frame_id) override {
5✔
75
        // Load the stored image data into the base class
76
        static_cast<void>(frame_id); // We only have one frame (frame 0)
77
        
78
        if (is8Bit() && !_stored_image_8bit.empty()) {
5✔
79
            setRawData(_stored_image_8bit);
3✔
80
        } else if (is32Bit() && !_stored_image_32bit.empty()) {
2✔
81
            setRawData(_stored_image_32bit);
2✔
82
        }
83
    }
5✔
84

85
private:
86
    std::vector<uint8_t> _stored_image_8bit;
87
    std::vector<float> _stored_image_32bit;
88
};
89

90
TEST_CASE("Data Transform: Line Alignment - FWHM displacement calculation - Core functionality", "[line][alignment][fwhm][transform]") {
14✔
91
    
92
    SECTION("Simple bright line detection") {
14✔
93
        // Create a simple test image with a bright horizontal line
94
        ImageSize image_size{100, 100};
1✔
95
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
96
        
97
        // Create a bright horizontal line at y=50
98
        for (int x = 0; x < 100; ++x) {
101✔
99
            image_data[50 * 100 + x] = 255; // Bright white line
100✔
100
        }
101
        
102
        // Test vertex at (50, 50) with perpendicular direction pointing up
103
        Point2D<float> vertex{50.0f, 50.0f};
1✔
104
        Point2D<float> perp_dir{0.0f, -1.0f}; // Pointing up
1✔
105
        
106
        Point2D<float> center_point = calculate_fwhm_center(
1✔
107
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
108
        
109
        // Should find the bright line and return center point at (50, 50) (already on the line)
110
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
111
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
112
    }
15✔
113
    
114
    SECTION("Coordinate system verification") {
14✔
115
        // Create a test image to verify coordinate system
116
        ImageSize image_size{10, 10};
1✔
117
        std::vector<uint8_t> image_data(100, 0); // 10x10 = 100 pixels
3✔
118
        
119
        // Set pixel at (5, 3) to white
120
        image_data[3 * 10 + 5] = 255; // y=3, x=5
1✔
121
        
122
        // Test that get_pixel_value returns correct value
123
        Point2D<float> test_point{5.0f, 3.0f};
1✔
124
        uint8_t pixel_value = get_pixel_value(test_point, image_data, image_size);
1✔
125
        REQUIRE(pixel_value == 255);
1✔
126
        
127
        // Test that get_pixel_value returns 0 for other positions
128
        Point2D<float> test_point2{4.0f, 3.0f};
1✔
129
        uint8_t pixel_value2 = get_pixel_value(test_point2, image_data, image_size);
1✔
130
        REQUIRE(pixel_value2 == 0);
1✔
131
    }
15✔
132
    
133
    SECTION("Bright line detection with offset") {
14✔
134
        // Create a test image with a bright horizontal line
135
        ImageSize image_size{100, 100};
1✔
136
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
137
        
138
        // Create a bright horizontal line at y=60
139
        for (int x = 0; x < 100; ++x) {
101✔
140
            image_data[60 * 100 + x] = 255; // Bright white line
100✔
141
        }
142
        
143
        // Test vertex at (50, 50) with perpendicular direction pointing up
144
        Point2D<float> vertex{50.0f, 50.0f};
1✔
145
        Point2D<float> perp_dir{0.0f, 1.0f}; // Pointing down
1✔
146
        
147
        Point2D<float> center_point = calculate_fwhm_center(
1✔
148
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
149
        
150
        // Should find the bright line at y=60 and return center point at (50, 60)
151
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
152
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
153
    }
15✔
154
    
155
    SECTION("Bright line detection with offset and thickness") {
14✔
156
        // Create a test image with a bright horizontal line with thickness
157
        ImageSize image_size{100, 100};
1✔
158
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
159
        
160
        // Create a bright horizontal line at y=60 with thickness of 3 pixels
161
        for (int x = 0; x < 100; ++x) {
101✔
162
            // Line spans from y=59 to y=61 (3 pixels thick)
163
            for (int y = 59; y <= 61; ++y) {
400✔
164
                if (y >= 0 && y < 100) {
300✔
165
                    image_data[y * 100 + x] = 255; // Bright white line
300✔
166
                }
167
            }
168
        }
169
        
170
        // Test vertex at (50, 50) with perpendicular direction pointing down
171
        Point2D<float> vertex{50.0f, 50.0f};
1✔
172
        Point2D<float> perp_dir{0.0f, 1.0f}; // Pointing down
1✔
173
        
174
        Point2D<float> center_point = calculate_fwhm_center(
1✔
175
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
176
        
177
        // Should find the center of the thick bright line at y=60 and return center point at (50, 60)
178
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
179
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
180
    }
15✔
181
    
182
    SECTION("Bright line detection with varying thickness") {
14✔
183
        // Create a test image with a bright horizontal line with varying thickness
184
        ImageSize image_size{100, 100};
1✔
185
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
186
        
187
        // Create a bright horizontal line at y=60 with thickness of 5 pixels
188
        for (int x = 0; x < 100; ++x) {
101✔
189
            // Line spans from y=58 to y=62 (5 pixels thick)
190
            for (int y = 58; y <= 62; ++y) {
600✔
191
                if (y >= 0 && y < 100) {
500✔
192
                    image_data[y * 100 + x] = 255; // Bright white line
500✔
193
                }
194
            }
195
        }
196
        
197
        // Test vertex at (50, 50) with perpendicular direction pointing down
198
        Point2D<float> vertex{50.0f, 50.0f};
1✔
199
        Point2D<float> perp_dir{0.0f, 1.0f}; // Pointing down
1✔
200
        
201
        Point2D<float> center_point = calculate_fwhm_center(
1✔
202
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
203
        
204
        // Should find the center of the thick bright line at y=60 and return center point at (50, 60)
205
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
206
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
207
    }
15✔
208
    
209
    SECTION("Bright line detection with very thick line") {
14✔
210
        // Create a test image with a very thick bright horizontal line
211
        ImageSize image_size{100, 100};
1✔
212
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
213
        
214
        // Create a bright horizontal line at y=60 with thickness of 9 pixels
215
        for (int x = 0; x < 100; ++x) {
101✔
216
            // Line spans from y=56 to y=64 (9 pixels thick)
217
            for (int y = 56; y <= 64; ++y) {
1,000✔
218
                if (y >= 0 && y < 100) {
900✔
219
                    image_data[y * 100 + x] = 255; // Bright white line
900✔
220
                }
221
            }
222
        }
223
        
224
        // Test vertex at (50, 50) with perpendicular direction pointing down
225
        Point2D<float> vertex{50.0f, 50.0f};
1✔
226
        Point2D<float> perp_dir{0.0f, 1.0f}; // Pointing down
1✔
227
        
228
        Point2D<float> center_point = calculate_fwhm_center(
1✔
229
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
230
        
231
        // Should find the center of the thick bright line at y=60 and return center point at (50, 60)
232
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
233
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
234
    }
15✔
235
    
236
    SECTION("Bright line detection with diagonal perpendicular") {
14✔
237
        // Create a test image with a bright diagonal line
238
        ImageSize image_size{100, 100};
1✔
239
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
240
        
241
        // Create a bright diagonal line
242
        for (int i = 0; i < 100; ++i) {
101✔
243
            int x = i;
100✔
244
            int y = i + 10; // Offset by 10 pixels
100✔
245
            if (x < 100 && y < 100) {
100✔
246
                image_data[y * 100 + x] = 255; // Bright white line
90✔
247
            }
248
        }
249
        
250
        // Test vertex at (50, 50) with perpendicular direction
251
        Point2D<float> vertex{50.0f, 50.0f};
1✔
252
        Point2D<float> perp_dir{-0.707f, 0.707f}; // Diagonal perpendicular
1✔
253
        
254
        Point2D<float> center_point = calculate_fwhm_center(
1✔
255
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
256
        
257
        // Should find the bright line and return appropriate center point
258
        // This will be the nearest position of the diagonal line stretching from
259
        // (40, 50) to (50, 60) . This is (45, 55)
260
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(45.0f, 2.0f));
1✔
261
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(55.0f, 2.0f));
1✔
262
    }
15✔
263
    
264
    SECTION("Bright diagonal line detection with thickness") {
14✔
265
        // Create a test image with a bright diagonal line with thickness
266
        ImageSize image_size{100, 100};
1✔
267
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
268
        
269
        // Create a bright diagonal line from (10,10) to (90,90) with thickness
270
        for (int i = 10; i <= 90; ++i) {
82✔
271
            // Line spans from (i,i) with thickness of 3 pixels perpendicular to the line
272
            for (int offset = -1; offset <= 1; ++offset) {
324✔
273
                int x = i + offset;
243✔
274
                int y = i + offset;
243✔
275
                if (x >= 0 && x < 100 && y >= 0 && y < 100) {
243✔
276
                    image_data[y * 100 + x] = 255; // Bright white line
243✔
277
                }
278
            }
279
        }
280
        
281
        // Test vertex at (50, 50) with perpendicular direction
282
        Point2D<float> vertex{50.0f, 50.0f};
1✔
283
        Point2D<float> perp_dir{-0.707f, 0.707f}; // Perpendicular to diagonal
1✔
284
        
285
        Point2D<float> center_point = calculate_fwhm_center(
1✔
286
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
287
        
288
        // Should find the diagonal line and return appropriate center point
289
        // The exact value depends on the FWHM calculation, but should be reasonable
290
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 5.0f));
1✔
291
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 5.0f));
1✔
292
    }
15✔
293
    
294
    SECTION("No bright features - should return zero") {
14✔
295
        // Create a completely black image
296
        ImageSize image_size{100, 100};
1✔
297
        std::vector<uint8_t> image_data(100 * 100, 0); // All black
3✔
298
        
299
        Point2D<float> vertex{50.0f, 50.0f};
1✔
300
        Point2D<float> perp_dir{0.0f, 1.0f}; // Pointing down
1✔
301
        
302
        Point2D<float> center_point = calculate_fwhm_center(
1✔
303
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
304
        
305
        // Should return original vertex when no bright features are found
306
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
307
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
308
    }
15✔
309
    
310
    SECTION("Bright spot detection") {
14✔
311
        // Create a test image with a bright spot
312
        ImageSize image_size{100, 100};
1✔
313
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
314
        
315
        // Create a bright spot at (60, 50)
316
        for (int y = 45; y <= 55; ++y) {
12✔
317
            for (int x = 55; x <= 65; ++x) {
132✔
318
                if (x >= 0 && x < 100 && y >= 0 && y < 100) {
121✔
319
                    image_data[y * 100 + x] = 255; // Bright white spot
121✔
320
                }
321
            }
322
        }
323
        
324
        // Test vertex at (50, 50) with perpendicular direction pointing right
325
        Point2D<float> vertex{50.0f, 50.0f};
1✔
326
        Point2D<float> perp_dir{1.0f, 0.0f}; // Pointing right
1✔
327
        
328
        Point2D<float> center_point = calculate_fwhm_center(
1✔
329
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
330
        
331
        // Should find the bright spot and return center point at (60, 50)
332
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
333
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
334
    }
15✔
335
    
336
    SECTION("Multiple bright lines - should find the closest") {
14✔
337
        // Create a test image with multiple bright horizontal lines
338
        ImageSize image_size{100, 100};
1✔
339
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
340
        
341
        // Create bright horizontal lines at y=30 and y=70
342
        for (int x = 0; x < 100; ++x) {
101✔
343
            image_data[30 * 100 + x] = 255; // Bright white line
100✔
344
            image_data[80 * 100 + x] = 255; // Bright white line
100✔
345
        }
346
        
347
        // Test vertex at (50, 50) with perpendicular direction pointing up
348
        Point2D<float> vertex{50.0f, 50.0f};
1✔
349
        Point2D<float> perp_dir{0.0f, -1.0f}; // Pointing up
1✔
350
        
351
        Point2D<float> center_point = calculate_fwhm_center(
1✔
352
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
353
        
354
        // Should find the closer line at y=30 and return center point at (50, 30)
355
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
356
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(30.0f, 1.0f));
1✔
357
    }
15✔
358
    
359
    SECTION("Edge case - vertex at image boundary") {
14✔
360
        // Create a test image with a bright line
361
        ImageSize image_size{100, 100};
1✔
362
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
363
        
364
        // Create a bright horizontal line at y=50
365
        for (int x = 0; x < 100; ++x) {
101✔
366
            image_data[50 * 100 + x] = 255; // Bright white line
100✔
367
        }
368
        
369
        // Test vertex at (50, 0) with perpendicular direction pointing right
370
        Point2D<float> vertex{50.0f, 0.0f};
1✔
371
        Point2D<float> perp_dir{0.0f, 1.0f}; // Pointing down
1✔
372

373
        Point2D<float> center_point = calculate_fwhm_center(
1✔
374
            vertex, perp_dir, 10, 102, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
375
        
376
        // Should handle boundary case gracefully
377
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
378
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
379
    }
15✔
380
    
381
    SECTION("Different perpendicular range values") {
14✔
382
        // Create a test image with a bright horizontal line
383
        ImageSize image_size{100, 100};
1✔
384
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
385
        
386
        // Create a bright horizontal line at y=60
387
        for (int x = 0; x < 100; ++x) {
101✔
388
            image_data[60 * 100 + x] = 255; // Bright white line
100✔
389
        }
390
        
391
        Point2D<float> vertex{50.0f, 50.0f};
1✔
392
        Point2D<float> perp_dir{0.0f, 1.0f}; // Pointing down
1✔
393
        
394
        // Test with different perpendicular ranges
395
        Point2D<float> center_point1 = calculate_fwhm_center(
1✔
396
            vertex, perp_dir, 10, 20, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
397
        
398
        Point2D<float> center_point2 = calculate_fwhm_center(
1✔
399
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
400
        
401
        // Both should find the same line and return similar center points
402
        REQUIRE_THAT(center_point1.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
403
        REQUIRE_THAT(center_point1.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
404
        REQUIRE_THAT(center_point2.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
405
        REQUIRE_THAT(center_point2.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
406
    }
15✔
407
    
408
    SECTION("Different width values") {
14✔
409
        // Create a test image with a bright horizontal line
410
        ImageSize image_size{100, 100};
1✔
411
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
412
        
413
        // Create a bright horizontal line at y=60
414
        for (int x = 0; x < 100; ++x) {
101✔
415
            image_data[60 * 100 + x] = 255; // Bright white line
100✔
416
        }
417
        
418
        Point2D<float> vertex{50.0f, 50.0f};
1✔
419
        Point2D<float> perp_dir{0.0f, 1.0f}; // Pointing down
1✔
420
        
421
        // Test with different width values
422
        Point2D<float> center_point1 = calculate_fwhm_center(
1✔
423
            vertex, perp_dir, 5, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
424
        
425
        Point2D<float> center_point2 = calculate_fwhm_center(
1✔
426
            vertex, perp_dir, 20, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
427
        
428
        // Both should find the same line and return similar center points
429
        REQUIRE_THAT(center_point1.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
430
        REQUIRE_THAT(center_point1.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
431
        REQUIRE_THAT(center_point2.x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
432
        REQUIRE_THAT(center_point2.y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
433
    }
15✔
434
}
14✔
435

436
TEST_CASE("Data Transform: Line Alignment - Perpendicular direction calculation - Core functionality", "[line][alignment][perpendicular][transform]") {
7✔
437
    
438
    SECTION("Horizontal line - perpendicular should be vertical") {
7✔
439
        // Create a horizontal line from (0,0) to (10,0)
440
        Line2D horizontal_line;
1✔
441
        horizontal_line.push_back(Point2D<float>{0.0f, 0.0f});
1✔
442
        horizontal_line.push_back(Point2D<float>{10.0f, 0.0f});
1✔
443
        
444
        // Test first vertex (should use direction to next vertex)
445
        Point2D<float> perp_dir_first = calculate_perpendicular_direction(horizontal_line, 0);
1✔
446
        REQUIRE_THAT(perp_dir_first.x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
447
        REQUIRE_THAT(perp_dir_first.y, Catch::Matchers::WithinAbs(1.0f, 0.001f)); // Pointing down
1✔
448
        
449
        // Test last vertex (should use direction from previous vertex)
450
        Point2D<float> perp_dir_last = calculate_perpendicular_direction(horizontal_line, 1);
1✔
451
        REQUIRE_THAT(perp_dir_last.x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
452
        REQUIRE_THAT(perp_dir_last.y, Catch::Matchers::WithinAbs(1.0f, 0.001f)); // Pointing down
1✔
453
    }
8✔
454
    
455
    SECTION("Vertical line - perpendicular should be horizontal") {
7✔
456
        // Create a vertical line from (0,0) to (0,10)
457
        Line2D vertical_line;
1✔
458
        vertical_line.push_back(Point2D<float>{0.0f, 0.0f});
1✔
459
        vertical_line.push_back(Point2D<float>{0.0f, 10.0f});
1✔
460
        
461
        // Test first vertex
462
        Point2D<float> perp_dir_first = calculate_perpendicular_direction(vertical_line, 0);
1✔
463
        REQUIRE_THAT(perp_dir_first.x, Catch::Matchers::WithinAbs(-1.0f, 0.001f)); // Pointing left
1✔
464
        REQUIRE_THAT(perp_dir_first.y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
465
        
466
        // Test last vertex
467
        Point2D<float> perp_dir_last = calculate_perpendicular_direction(vertical_line, 1);
1✔
468
        REQUIRE_THAT(perp_dir_last.x, Catch::Matchers::WithinAbs(-1.0f, 0.001f)); // Pointing left
1✔
469
        REQUIRE_THAT(perp_dir_last.y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
470
    }
8✔
471
    
472
    SECTION("Diagonal line - perpendicular should be perpendicular") {
7✔
473
        // Create a diagonal line from (0,0) to (10,10)
474
        Line2D diagonal_line;
1✔
475
        diagonal_line.push_back(Point2D<float>{0.0f, 0.0f});
1✔
476
        diagonal_line.push_back(Point2D<float>{10.0f, 10.0f});
1✔
477
        
478
        // Test first vertex
479
        Point2D<float> perp_dir_first = calculate_perpendicular_direction(diagonal_line, 0);
1✔
480
        // Perpendicular to (10,10) - (0,0) = (10,10) should be (-10,10) normalized
481
        REQUIRE_THAT(perp_dir_first.x, Catch::Matchers::WithinAbs(-0.707f, 0.001f)); // -1/sqrt(2)
1✔
482
        REQUIRE_THAT(perp_dir_first.y, Catch::Matchers::WithinAbs(0.707f, 0.001f));  // 1/sqrt(2)
1✔
483
        
484
        // Test last vertex
485
        Point2D<float> perp_dir_last = calculate_perpendicular_direction(diagonal_line, 1);
1✔
486
        REQUIRE_THAT(perp_dir_last.x, Catch::Matchers::WithinAbs(-0.707f, 0.001f));
1✔
487
        REQUIRE_THAT(perp_dir_last.y, Catch::Matchers::WithinAbs(0.707f, 0.001f));
1✔
488
    }
8✔
489
    
490
    SECTION("Multi-segment line - middle vertices average perpendiculars") {
7✔
491
        // Create a line with three segments: horizontal, diagonal, vertical
492
        Line2D multi_line;
1✔
493
        multi_line.push_back(Point2D<float>{0.0f, 0.0f});   // Start
1✔
494
        multi_line.push_back(Point2D<float>{10.0f, 0.0f});  // Horizontal segment
1✔
495
        multi_line.push_back(Point2D<float>{20.0f, 10.0f}); // Diagonal segment
1✔
496
        multi_line.push_back(Point2D<float>{20.0f, 20.0f}); // Vertical segment
1✔
497
        
498
        // Test middle vertex (index 1) - should average perpendiculars from adjacent segments
499
        Point2D<float> perp_dir_middle = calculate_perpendicular_direction(multi_line, 1);
1✔
500
        
501
        // First segment: (10,0) - (0,0) = (10,0), perpendicular = (0,1)
502
        // Second segment: (20,10) - (10,0) = (10,10), perpendicular = (-10,10)/sqrt(200) = (-0.707, 0.707)
503
        // Average: (0 + (-0.707))/2 = -0.3535, (1 + 0.707)/2 = 0.8535
504
        // Normalized: approximately (-0.383, 0.924)
505
        REQUIRE_THAT(perp_dir_middle.x, Catch::Matchers::WithinAbs(-0.383f, 0.1f));
1✔
506
        REQUIRE_THAT(perp_dir_middle.y, Catch::Matchers::WithinAbs(0.924f, 0.1f));
1✔
507
    }
8✔
508
    
509
    SECTION("Line with fewer than 2 points - should return zero vector") {
7✔
510
        // Create a line with only 1 point
511
        Line2D short_line;
1✔
512
        short_line.push_back(Point2D<float>{0.0f, 0.0f});
1✔
513
        
514
        // Test with 1 point (invalid for perpendicular calculation)
515
        Point2D<float> perp_dir = calculate_perpendicular_direction(short_line, 0);
1✔
516
        REQUIRE_THAT(perp_dir.x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
517
        REQUIRE_THAT(perp_dir.y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
518
    }
8✔
519
    
520
    SECTION("Line with zero-length segments - should return zero vector") {
7✔
521
        // Create a line with zero-length segments
522
        Line2D zero_line;
1✔
523
        zero_line.push_back(Point2D<float>{5.0f, 5.0f});
1✔
524
        zero_line.push_back(Point2D<float>{5.0f, 5.0f}); // Same point
1✔
525
        zero_line.push_back(Point2D<float>{5.0f, 5.0f}); // Same point
1✔
526
        
527
        // Test middle vertex
528
        Point2D<float> perp_dir = calculate_perpendicular_direction(zero_line, 1);
1✔
529
        REQUIRE_THAT(perp_dir.x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
530
        REQUIRE_THAT(perp_dir.y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
531
    }
8✔
532
    
533
    SECTION("Normalized perpendicular vectors") {
7✔
534
        // Test that all perpendicular vectors are normalized (unit length)
535
        Line2D test_line;
1✔
536
        test_line.push_back(Point2D<float>{0.0f, 0.0f});
1✔
537
        test_line.push_back(Point2D<float>{3.0f, 4.0f}); // 3-4-5 triangle
1✔
538
        test_line.push_back(Point2D<float>{6.0f, 8.0f});
1✔
539
        
540
        // Test first vertex
541
        Point2D<float> perp_dir = calculate_perpendicular_direction(test_line, 0);
1✔
542
        float length = std::sqrt(perp_dir.x * perp_dir.x + perp_dir.y * perp_dir.y);
1✔
543
        REQUIRE_THAT(length, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
544
        
545
        // Test middle vertex
546
        Point2D<float> perp_dir_middle = calculate_perpendicular_direction(test_line, 1);
1✔
547
        float length_middle = std::sqrt(perp_dir_middle.x * perp_dir_middle.x + perp_dir_middle.y * perp_dir_middle.y);
1✔
548
        REQUIRE_THAT(length_middle, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
549
        
550
        // Test last vertex
551
        Point2D<float> perp_dir_last = calculate_perpendicular_direction(test_line, 2);
1✔
552
        float length_last = std::sqrt(perp_dir_last.x * perp_dir_last.x + perp_dir_last.y * perp_dir_last.y);
1✔
553
        REQUIRE_THAT(length_last, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
554
    }
8✔
555
}
7✔
556

557
TEST_CASE("Data Transform: Line Alignment - FWHM center calculation - Edge cases and error handling", "[line][alignment][fwhm][edge]") {
4✔
558
    
559
    SECTION("Zero width parameter") {
4✔
560
        ImageSize image_size{100, 100};
1✔
561
        std::vector<uint8_t> image_data(100 * 100, 0);
3✔
562
        
563
        Point2D<float> vertex{50.0f, 50.0f};
1✔
564
        Point2D<float> perp_dir{0.0f, 1.0f};
1✔
565
        
566
        Point2D<float> center_point = calculate_fwhm_center(
1✔
567
            vertex, perp_dir, 0, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
568
        
569
        // Should return original vertex for invalid width
570
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
571
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
572
    }
5✔
573
    
574
    SECTION("Zero perpendicular direction") {
4✔
575
        ImageSize image_size{100, 100};
1✔
576
        std::vector<uint8_t> image_data(100 * 100, 0);
3✔
577
        
578
        Point2D<float> vertex{50.0f, 50.0f};
1✔
579
        Point2D<float> perp_dir{0.0f, 0.0f}; // Zero direction
1✔
580
        
581
        Point2D<float> center_point = calculate_fwhm_center(
1✔
582
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
583
        
584
        // Should handle zero direction gracefully
585
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
586
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
587
    }
5✔
588
    
589
    SECTION("Empty image data") {
4✔
590
        ImageSize image_size{100, 100};
1✔
591
        std::vector<uint8_t> image_data; // Empty
1✔
592
        
593
        Point2D<float> vertex{50.0f, 50.0f};
1✔
594
        Point2D<float> perp_dir{0.0f, 1.0f};
1✔
595
        
596
        Point2D<float> center_point = calculate_fwhm_center(
1✔
597
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
598
        
599
        // Should handle empty data gracefully
600
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
601
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(50.0f, 0.001f));
1✔
602
    }
5✔
603
    
604
    SECTION("Vertex outside image bounds") {
4✔
605
        ImageSize image_size{100, 100};
1✔
606
        std::vector<uint8_t> image_data(100 * 100, 0);
3✔
607
        
608
        Point2D<float> vertex{150.0f, 150.0f}; // Outside bounds
1✔
609
        Point2D<float> perp_dir{0.0f, 1.0f};
1✔
610
        
611
        Point2D<float> center_point = calculate_fwhm_center(
1✔
612
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
613
        
614
        // Should handle out-of-bounds vertex gracefully
615
        REQUIRE_THAT(center_point.x, Catch::Matchers::WithinAbs(150.0f, 0.001f));
1✔
616
        REQUIRE_THAT(center_point.y, Catch::Matchers::WithinAbs(150.0f, 0.001f));
1✔
617
    }
5✔
618
}
4✔
619

620
#if LINE_ALIGNMENT_DEBUG_MODE
621
TEST_CASE("Data Transform: Line Alignment - FWHM profile extents calculation - Debug mode", "[line][alignment][fwhm][debug]") {
622
    
623
    SECTION("Debug mode profile extents for bright line") {
624
        // Create a test image with a bright horizontal line
625
        ImageSize image_size{100, 100};
626
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
627
        
628
        // Create a bright horizontal line at y=60
629
        for (int x = 0; x < 100; ++x) {
630
            image_data[60 * 100 + x] = 255; // Bright white line
631
        }
632
        
633
        // Test vertex at (50, 50) with perpendicular direction pointing down
634
        Point2D<float> vertex{50.0f, 50.0f};
635
        Point2D<float> perp_dir{0.0f, 1.0f}; // Pointing down
636
        
637
        Line2D debug_line = calculate_fwhm_profile_extents(
638
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
639
        
640
        // Should return a line with 3 points: [left_extent, max_point, right_extent]
641
        REQUIRE(debug_line.size() == 3);
642
        
643
        // First point should be left extent (around y=55-65)
644
        REQUIRE_THAT(debug_line[0].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
645
        REQUIRE_THAT(debug_line[0].y, Catch::Matchers::WithinAbs(60.0f, 5.0f));
646
        
647
        // Second point should be max point (around y=60)
648
        REQUIRE_THAT(debug_line[1].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
649
        REQUIRE_THAT(debug_line[1].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
650
        
651
        // Third point should be right extent (around y=55-65)
652
        REQUIRE_THAT(debug_line[2].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
653
        REQUIRE_THAT(debug_line[2].y, Catch::Matchers::WithinAbs(60.0f, 5.0f));
654
    }
655
    
656
    SECTION("Debug mode with multiple maximum values") {
657
        // Create a test image with a bright horizontal line that has multiple maximum values
658
        ImageSize image_size{100, 100};
659
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
660
        
661
        // Create a bright horizontal line at y=60 with some thickness
662
        for (int x = 0; x < 100; ++x) {
663
            // Create a line with multiple maximum values at different positions
664
            for (int y = 58; y <= 62; ++y) {
665
                if (y >= 0 && y < 100) {
666
                    image_data[y * 100 + x] = 255; // Bright white line
667
                }
668
            }
669
        }
670
        
671
        // Test vertex at (50, 50) with perpendicular direction pointing down
672
        Point2D<float> vertex{50.0f, 50.0f};
673
        Point2D<float> perp_dir{0.0f, 1.0f}; // Pointing down
674
        
675
        Line2D debug_line = calculate_fwhm_profile_extents(
676
            vertex, perp_dir, 10, 50, image_data, image_size, FWHMApproach::PEAK_WIDTH_HALF_MAX);
677
        
678
        // Should return a line with 3 points: [left_extent, max_point, right_extent]
679
        REQUIRE(debug_line.size() == 3);
680
        
681
        // First point should be left extent
682
        REQUIRE_THAT(debug_line[0].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
683
        REQUIRE_THAT(debug_line[0].y, Catch::Matchers::WithinAbs(60.0f, 5.0f));
684
        
685
        // Second point should be max point (average of multiple max positions)
686
        REQUIRE_THAT(debug_line[1].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
687
        REQUIRE_THAT(debug_line[1].y, Catch::Matchers::WithinAbs(60.0f, 2.0f)); // Should be around center of thick line
688
        
689
        // Third point should be right extent
690
        REQUIRE_THAT(debug_line[2].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
691
        REQUIRE_THAT(debug_line[2].y, Catch::Matchers::WithinAbs(60.0f, 5.0f));
692
    }
693
}
694
#endif
695

696
#include "DataManager.hpp"
697
#include "IO/LoaderRegistry.hpp"
698
#include "transforms/TransformPipeline.hpp"
699
#include "transforms/TransformRegistry.hpp"
700
#include "Lines/Line_Data.hpp"
701
#include "Media/Media_Data.hpp"
702

703
#include <filesystem>
704
#include <fstream>
705
#include <iostream>
706

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

711
    // Create a TimeFrame for our data
712
    auto time_frame = std::make_shared<TimeFrame>();
1✔
713
    dm.setTime(TimeKey("default"), time_frame);
1✔
714
    
715
    // Create test line data - a simple horizontal line
716
    Line2D test_line;
1✔
717
    test_line.push_back(Point2D<float>{10.0f, 50.0f});
1✔
718
    test_line.push_back(Point2D<float>{50.0f, 50.0f});
1✔
719
    test_line.push_back(Point2D<float>{90.0f, 50.0f});
1✔
720
    
721
    auto test_line_data = std::make_shared<LineData>();
1✔
722
    test_line_data->setTimeFrame(time_frame);
1✔
723
    test_line_data->addAtTime(TimeFrameIndex(0), test_line);
1✔
724
    
725
    // Store the line data in DataManager with a known key
726
    dm.setData("test_line", test_line_data, TimeKey("default"));
3✔
727
    
728
    // Create test media data with a bright horizontal line at y=60
729
    auto media_data = std::make_shared<MockMediaData>();
1✔
730
    media_data->setTimeFrame(time_frame);
1✔
731
    
732
    // Create a test image with a bright horizontal line at y=60
733
    ImageSize image_size{100, 100};
1✔
734
    std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
735
    
736
    // Create a bright horizontal line at y=60
737
    for (int x = 0; x < 100; ++x) {
101✔
738
        image_data[60 * 100 + x] = 255; // Bright white line
100✔
739
    }
740
    
741
    // Add the image to media data
742
    media_data->addImage(image_data, image_size);
1✔
743
    
744
    // Store the media data in DataManager with a known key
745
    dm.setData("test_media", media_data, TimeKey("default"));
3✔
746
    
747
    // Create JSON configuration for transformation pipeline using unified format
748
    const char* json_config = 
1✔
749
        "[\n"
750
        "{\n"
751
        "    \"transformations\": {\n"
752
        "        \"metadata\": {\n"
753
        "            \"name\": \"Line Alignment Pipeline\",\n"
754
        "            \"description\": \"Test line alignment to bright features in media\",\n"
755
        "            \"version\": \"1.0\"\n"
756
        "        },\n"
757
        "        \"steps\": [\n"
758
        "            {\n"
759
        "                \"step_id\": \"1\",\n"
760
        "                \"transform_name\": \"Line Alignment to Bright Features\",\n"
761
        "                \"phase\": \"analysis\",\n"
762
        "                \"input_key\": \"test_line\",\n"
763
        "                \"output_key\": \"aligned_line\",\n"
764
        "                \"parameters\": {\n"
765
        "                    \"media_data\": \"test_media\",\n"
766
        "                    \"width\": 20,\n"
767
        "                    \"perpendicular_range\": 50,\n"
768
        "                    \"use_processed_data\": true,\n"
769
        "                    \"approach\": \"PEAK_WIDTH_HALF_MAX\",\n"
770
        "                    \"output_mode\": \"ALIGNED_VERTICES\"\n"
771
        "                }\n"
772
        "            }\n"
773
        "        ]\n"
774
        "    }\n"
775
        "}\n"
776
        "]";
777
    
778
    // Create temporary directory and write JSON config to file
779
    std::filesystem::path test_dir = std::filesystem::temp_directory_path() / "line_alignment_pipeline_test";
1✔
780
    std::filesystem::create_directories(test_dir);
1✔
781
    
782
    std::filesystem::path json_filepath = test_dir / "pipeline_config.json";
1✔
783
    {
784
        std::ofstream json_file(json_filepath);
1✔
785
        REQUIRE(json_file.is_open());
1✔
786
        json_file << json_config;
1✔
787
        json_file.close();
1✔
788
    }
1✔
789
    
790
    // Execute the transformation pipeline using load_data_from_json_config
791
    auto data_info_list = load_data_from_json_config(&dm, json_filepath.string());
1✔
792
    
793
    // Verify the transformation was executed and results are available
794
    auto result_line = dm.getData<LineData>("aligned_line");
3✔
795
    REQUIRE(result_line != nullptr);
1✔
796
    
797
    // Verify the line alignment results
798
    // The original line was at y=50, but there's a bright line at y=60
799
    // The aligned line should have vertices moved to y=60
800
    auto aligned_lines = result_line->getAtTime(TimeFrameIndex(0));
1✔
801
    REQUIRE(aligned_lines.size() == 1);
1✔
802
    auto aligned_vertices = aligned_lines[0];
1✔
803
    REQUIRE(aligned_vertices.size() == 3);
1✔
804
    
805
    // Check that vertices were aligned to the bright line at y=60
806
    REQUIRE_THAT(aligned_vertices[0].x, Catch::Matchers::WithinAbs(10.0f, 1.0f));
1✔
807
    REQUIRE_THAT(aligned_vertices[0].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
808
    REQUIRE_THAT(aligned_vertices[1].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
809
    REQUIRE_THAT(aligned_vertices[1].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
810
    REQUIRE_THAT(aligned_vertices[2].x, Catch::Matchers::WithinAbs(90.0f, 1.0f));
1✔
811
    REQUIRE_THAT(aligned_vertices[2].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
812
    
813
    // Test another pipeline with different parameters (different width and range)
814
    const char* json_config_different_params = 
1✔
815
        "[\n"
816
        "{\n"
817
        "    \"transformations\": {\n"
818
        "        \"metadata\": {\n"
819
        "            \"name\": \"Line Alignment with Different Parameters\",\n"
820
        "            \"description\": \"Test line alignment with different width and range\",\n"
821
        "            \"version\": \"1.0\"\n"
822
        "        },\n"
823
        "        \"steps\": [\n"
824
        "            {\n"
825
        "                \"step_id\": \"1\",\n"
826
        "                \"transform_name\": \"Line Alignment to Bright Features\",\n"
827
        "                \"phase\": \"analysis\",\n"
828
        "                \"input_key\": \"test_line\",\n"
829
        "                \"output_key\": \"aligned_line_different_params\",\n"
830
        "                \"parameters\": {\n"
831
        "                    \"media_data\": \"test_media\",\n"
832
        "                    \"width\": 10,\n"
833
        "                    \"perpendicular_range\": 30,\n"
834
        "                    \"use_processed_data\": true,\n"
835
        "                    \"approach\": \"PEAK_WIDTH_HALF_MAX\",\n"
836
        "                    \"output_mode\": \"ALIGNED_VERTICES\"\n"
837
        "                }\n"
838
        "            }\n"
839
        "        ]\n"
840
        "    }\n"
841
        "}\n"
842
        "]";
843
    
844
    std::filesystem::path json_filepath_different_params = test_dir / "pipeline_config_different_params.json";
1✔
845
    {
846
        std::ofstream json_file(json_filepath_different_params);
1✔
847
        REQUIRE(json_file.is_open());
1✔
848
        json_file << json_config_different_params;
1✔
849
        json_file.close();
1✔
850
    }
1✔
851
    
852
    // Execute the different parameters pipeline
853
    auto data_info_list_different_params = load_data_from_json_config(&dm, json_filepath_different_params.string());
1✔
854
    
855
    // Verify the different parameters results
856
    auto result_line_different_params = dm.getData<LineData>("aligned_line_different_params");
3✔
857
    REQUIRE(result_line_different_params != nullptr);
1✔
858
    
859
    auto aligned_lines_different_params = result_line_different_params->getAtTime(TimeFrameIndex(0));
1✔
860
    REQUIRE(aligned_lines_different_params.size() == 1);
1✔
861
    auto aligned_vertices_different_params = aligned_lines_different_params[0];
1✔
862
    REQUIRE(aligned_vertices_different_params.size() == 3);
1✔
863
    
864
    // Should still align to the bright line at y=60
865
    REQUIRE_THAT(aligned_vertices_different_params[0].x, Catch::Matchers::WithinAbs(10.0f, 1.0f));
1✔
866
    REQUIRE_THAT(aligned_vertices_different_params[0].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
867
    REQUIRE_THAT(aligned_vertices_different_params[1].x, Catch::Matchers::WithinAbs(50.0f, 1.0f));
1✔
868
    REQUIRE_THAT(aligned_vertices_different_params[1].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
869
    REQUIRE_THAT(aligned_vertices_different_params[2].x, Catch::Matchers::WithinAbs(90.0f, 1.0f));
1✔
870
    REQUIRE_THAT(aligned_vertices_different_params[2].y, Catch::Matchers::WithinAbs(60.0f, 1.0f));
1✔
871
    
872
    // Test FWHM profile extents output mode
873
    const char* json_config_fwhm_extents = 
1✔
874
        "[\n"
875
        "{\n"
876
        "    \"transformations\": {\n"
877
        "        \"metadata\": {\n"
878
        "            \"name\": \"Line Alignment FWHM Extents\",\n"
879
        "            \"description\": \"Test line alignment with FWHM profile extents output\",\n"
880
        "            \"version\": \"1.0\"\n"
881
        "        },\n"
882
        "        \"steps\": [\n"
883
        "            {\n"
884
        "                \"step_id\": \"1\",\n"
885
        "                \"transform_name\": \"Line Alignment to Bright Features\",\n"
886
        "                \"phase\": \"analysis\",\n"
887
        "                \"input_key\": \"test_line\",\n"
888
        "                \"output_key\": \"fwhm_extents_line\",\n"
889
        "                \"parameters\": {\n"
890
        "                    \"media_data\": \"test_media\",\n"
891
        "                    \"width\": 20,\n"
892
        "                    \"perpendicular_range\": 50,\n"
893
        "                    \"use_processed_data\": true,\n"
894
        "                    \"approach\": \"PEAK_WIDTH_HALF_MAX\",\n"
895
        "                    \"output_mode\": \"FWHM_PROFILE_EXTENTS\"\n"
896
        "                }\n"
897
        "            }\n"
898
        "        ]\n"
899
        "    }\n"
900
        "}\n"
901
        "]";
902
    
903
    std::filesystem::path json_filepath_fwhm_extents = test_dir / "pipeline_config_fwhm_extents.json";
1✔
904
    {
905
        std::ofstream json_file(json_filepath_fwhm_extents);
1✔
906
        REQUIRE(json_file.is_open());
1✔
907
        json_file << json_config_fwhm_extents;
1✔
908
        json_file.close();
1✔
909
    }
1✔
910
    
911
    // Execute the FWHM extents pipeline
912
    auto data_info_list_fwhm_extents = load_data_from_json_config(&dm, json_filepath_fwhm_extents.string());
1✔
913
    
914
    // Verify the FWHM extents results
915
    auto result_line_fwhm_extents = dm.getData<LineData>("fwhm_extents_line");
3✔
916
    REQUIRE(result_line_fwhm_extents != nullptr);
1✔
917
    
918
    auto fwhm_extents_lines = result_line_fwhm_extents->getAtTime(TimeFrameIndex(0));
1✔
919
    REQUIRE(fwhm_extents_lines.size() == 3); // One line per vertex
1✔
920
    auto fwhm_extents_vertices = fwhm_extents_lines[0];
1✔
921
    // Should have 3 points per vertex: [left_extent, max_point, right_extent]
922
    // For 3 vertices, this should be 9 points total
923
    REQUIRE(fwhm_extents_vertices.size() == 3);
1✔
924
    
925
    // Cleanup
926
    try {
927
        std::filesystem::remove_all(test_dir);
1✔
UNCOV
928
    } catch (const std::exception& e) {
×
UNCOV
929
        std::cerr << "Warning: Cleanup failed: " << e.what() << std::endl;
×
UNCOV
930
    }
×
931
} 
2✔
932

933
TEST_CASE("Data Transform: Line Alignment - Line alignment with 8-bit and 32-bit media data", "[line][alignment][media][transform]") {
3✔
934
    
935
    SECTION("8-bit media data alignment") {
3✔
936
        // Create a test image with a bright horizontal line
937
        ImageSize image_size{100, 100};
1✔
938
        std::vector<uint8_t> image_data(100 * 100, 0); // All black initially
3✔
939
        
940
        // Create a bright horizontal line at y=60
941
        for (int x = 0; x < 100; ++x) {
101✔
942
            image_data[60 * 100 + x] = 255; // Bright white line
100✔
943
        }
944
        
945
        // Create mock media data with 8-bit data
946
        auto media_data = std::make_shared<MockMediaData>(MediaData::BitDepth::Bit8);
1✔
947
        media_data->addImage8(image_data, image_size);
1✔
948
        
949
        // Create test line data
950
        auto line_data = std::make_shared<LineData>();
1✔
951
        line_data->setImageSize(image_size);
1✔
952
        
953
        // Create a test line with vertices that should align to the bright line
954
        Line2D test_line;
1✔
955
        test_line.push_back(Point2D<float>{30.0f, 50.0f}); // Should move to y=60
1✔
956
        test_line.push_back(Point2D<float>{50.0f, 55.0f}); // Should move to y=60
1✔
957
        test_line.push_back(Point2D<float>{70.0f, 45.0f}); // Should move to y=60
1✔
958
        
959
        line_data->addAtTime(TimeFrameIndex(0), test_line, false);
1✔
960
        
961
        // Test the line alignment function
962
        auto aligned_line_data = line_alignment(
1✔
963
            line_data.get(),
1✔
964
            media_data.get(),
1✔
965
            20,  // width
966
            50,  // perpendicular_range
967
            false, // use_processed_data
968
            FWHMApproach::PEAK_WIDTH_HALF_MAX,
969
            LineAlignmentOutputMode::ALIGNED_VERTICES
970
        );
1✔
971
        
972
        REQUIRE(aligned_line_data != nullptr);
1✔
973
        
974
        // Check that the lines were aligned
975
        auto aligned_lines = aligned_line_data->getAtTime(TimeFrameIndex(0));
1✔
976
        REQUIRE(aligned_lines.size() == 1);
1✔
977
        
978
        auto const& aligned_line = aligned_lines[0];
1✔
979
        REQUIRE(aligned_line.size() == 3);
1✔
980
        
981
        // All vertices should be aligned to y=60 (the bright line)
982
        for (auto const& vertex : aligned_line) {
4✔
983
            REQUIRE_THAT(vertex.y, Catch::Matchers::WithinAbs(60.0f, 2.0f));
3✔
984
        }
985
    }
4✔
986
    
987
    SECTION("32-bit media data alignment") {
3✔
988
        // Create a test image with a bright horizontal line using 32-bit float data
989
        ImageSize image_size{100, 100};
1✔
990
        std::vector<float> image_data(100 * 100, 0.0f); // All black initially
3✔
991
        
992
        // Create a bright horizontal line at y=60 with float intensity
993
        for (int x = 0; x < 100; ++x) {
101✔
994
            image_data[60 * 100 + x] = 1.0f; // Bright white line (normalized to 0-1)
100✔
995
        }
996
        
997
        // Create mock media data with 32-bit data
998
        auto media_data = std::make_shared<MockMediaData>(MediaData::BitDepth::Bit32);
1✔
999
        media_data->addImage32(image_data, image_size);
1✔
1000
        
1001
        // Create test line data
1002
        auto line_data = std::make_shared<LineData>();
1✔
1003
        line_data->setImageSize(image_size);
1✔
1004
        
1005
        // Create a test line with vertices that should align to the bright line
1006
        Line2D test_line;
1✔
1007
        test_line.push_back(Point2D<float>{30.0f, 50.0f}); // Should move to y=60
1✔
1008
        test_line.push_back(Point2D<float>{50.0f, 55.0f}); // Should move to y=60
1✔
1009
        test_line.push_back(Point2D<float>{70.0f, 45.0f}); // Should move to y=60
1✔
1010
        
1011
        line_data->addAtTime(TimeFrameIndex(0), test_line, false);
1✔
1012
        
1013
        // Test the line alignment function
1014
        auto aligned_line_data = line_alignment(
1✔
1015
            line_data.get(),
1✔
1016
            media_data.get(),
1✔
1017
            20,  // width
1018
            50,  // perpendicular_range
1019
            false, // use_processed_data
1020
            FWHMApproach::PEAK_WIDTH_HALF_MAX,
1021
            LineAlignmentOutputMode::ALIGNED_VERTICES
1022
        );
1✔
1023
        
1024
        REQUIRE(aligned_line_data != nullptr);
1✔
1025
        
1026
        // Check that the lines were aligned
1027
        auto aligned_lines = aligned_line_data->getAtTime(TimeFrameIndex(0));
1✔
1028
        REQUIRE(aligned_lines.size() == 1);
1✔
1029
        
1030
        auto const& aligned_line = aligned_lines[0];
1✔
1031
        REQUIRE(aligned_line.size() == 3);
1✔
1032
        
1033
        // All vertices should be aligned to y=60 (the bright line)
1034
        for (auto const& vertex : aligned_line) {
4✔
1035
            REQUIRE_THAT(vertex.y, Catch::Matchers::WithinAbs(60.0f, 2.0f));
3✔
1036
        }
1037
    }
4✔
1038
    
1039
    SECTION("Compare 8-bit vs 32-bit alignment results") {
3✔
1040
        ImageSize image_size{100, 100};
1✔
1041
        
1042
        // Create identical bright horizontal lines in both formats
1043
        std::vector<uint8_t> image_data_8bit(100 * 100, 0);
3✔
1044
        std::vector<float> image_data_32bit(100 * 100, 0.0f);
3✔
1045
        
1046
        for (int x = 0; x < 100; ++x) {
101✔
1047
            image_data_8bit[60 * 100 + x] = 255;
100✔
1048
            image_data_32bit[60 * 100 + x] = 1.0f;
100✔
1049
        }
1050
        
1051
        // Create mock media data for both bit depths
1052
        auto media_data_8bit = std::make_shared<MockMediaData>(MediaData::BitDepth::Bit8);
1✔
1053
        media_data_8bit->addImage8(image_data_8bit, image_size);
1✔
1054
        
1055
        auto media_data_32bit = std::make_shared<MockMediaData>(MediaData::BitDepth::Bit32);
1✔
1056
        media_data_32bit->addImage32(image_data_32bit, image_size);
1✔
1057
        
1058
        // Create identical test line data for both tests
1059
        auto line_data = std::make_shared<LineData>();
1✔
1060
        line_data->setImageSize(image_size);
1✔
1061
        
1062
        Line2D test_line;
1✔
1063
        test_line.push_back(Point2D<float>{30.0f, 50.0f});
1✔
1064
        test_line.push_back(Point2D<float>{50.0f, 55.0f});
1✔
1065
        test_line.push_back(Point2D<float>{70.0f, 45.0f});
1✔
1066
        
1067
        line_data->addAtTime(TimeFrameIndex(0), test_line, false);
1✔
1068
        
1069
        // Test alignment with 8-bit data
1070
        auto aligned_line_data_8bit = line_alignment(
1✔
1071
            line_data.get(),
1✔
1072
            media_data_8bit.get(),
1✔
1073
            20, 50, false,
1074
            FWHMApproach::PEAK_WIDTH_HALF_MAX,
1075
            LineAlignmentOutputMode::ALIGNED_VERTICES
1076
        );
1✔
1077
        
1078
        // Test alignment with 32-bit data
1079
        auto aligned_line_data_32bit = line_alignment(
1✔
1080
            line_data.get(),
1✔
1081
            media_data_32bit.get(),
1✔
1082
            20, 50, false,
1083
            FWHMApproach::PEAK_WIDTH_HALF_MAX,
1084
            LineAlignmentOutputMode::ALIGNED_VERTICES
1085
        );
1✔
1086
        
1087
        REQUIRE(aligned_line_data_8bit != nullptr);
1✔
1088
        REQUIRE(aligned_line_data_32bit != nullptr);
1✔
1089
        
1090
        // Get aligned lines from both results
1091
        auto aligned_lines_8bit = aligned_line_data_8bit->getAtTime(TimeFrameIndex(0));
1✔
1092
        auto aligned_lines_32bit = aligned_line_data_32bit->getAtTime(TimeFrameIndex(0));
1✔
1093
        
1094
        REQUIRE(aligned_lines_8bit.size() == 1);
1✔
1095
        REQUIRE(aligned_lines_32bit.size() == 1);
1✔
1096
        
1097
        auto const& aligned_line_8bit = aligned_lines_8bit[0];
1✔
1098
        auto const& aligned_line_32bit = aligned_lines_32bit[0];
1✔
1099
        
1100
        REQUIRE(aligned_line_8bit.size() == 3);
1✔
1101
        REQUIRE(aligned_line_32bit.size() == 3);
1✔
1102
        
1103
        // Results should be very similar between 8-bit and 32-bit
1104
        for (size_t i = 0; i < 3; ++i) {
4✔
1105
            REQUIRE_THAT(aligned_line_8bit[i].x, Catch::Matchers::WithinAbs(aligned_line_32bit[i].x, 1.0f));
3✔
1106
            REQUIRE_THAT(aligned_line_8bit[i].y, Catch::Matchers::WithinAbs(aligned_line_32bit[i].y, 1.0f));
3✔
1107
        }
1108
    }
4✔
1109
} 
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