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

paulmthompson / WhiskerToolbox / 17347485457

30 Aug 2025 07:03PM UTC coverage: 71.575% (+1.5%) from 70.094%
17347485457

push

github

paulmthompson
line subsegment testing added

389 of 392 new or added lines in 2 files covered. (99.23%)

4 existing lines in 1 file now uncovered.

31379 of 43841 relevant lines covered (71.57%)

1406.64 hits per line

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

99.21
/src/DataManager/transforms/Lines/Line_Subsegment/line_subsegment.test.cpp
1
#include "catch2/catch_test_macros.hpp"
2
#include "catch2/matchers/catch_matchers_vector.hpp"
3
#include "catch2/matchers/catch_matchers_floating_point.hpp"
4

5
#include "Lines/Line_Data.hpp"
6
#include "transforms/Lines/Line_Subsegment/line_subsegment.hpp"
7
#include "transforms/data_transforms.hpp" // For ProgressCallback
8

9
#include <vector>
10
#include <memory> // std::make_shared
11
#include <functional> // std::function
12

13
TEST_CASE("Data Transform: Extract Line Subsegment - Happy Path", "[transforms][line_subsegment]") {
5✔
14
    std::shared_ptr<LineData> line_data;
5✔
15
    std::shared_ptr<LineData> result_subsegments;
5✔
16
    LineSubsegmentParameters params;
5✔
17
    volatile int progress_val = -1; // Volatile to prevent optimization issues in test
5✔
18
    volatile int call_count = 0;    // Volatile for the same reason
5✔
19
    ProgressCallback cb = [&](int p) {
10✔
20
        progress_val = p;
3✔
21
        call_count = call_count + 1;
3✔
22
    };
5✔
23

24
    SECTION("Direct method - middle subsegment") {
5✔
25
        line_data = std::make_shared<LineData>();
1✔
26
        std::vector<float> x_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
27
        std::vector<float> y_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
28
        line_data->addAtTime(TimeFrameIndex(100), x_coords, y_coords);
1✔
29
        
30
        params.start_position = 0.2f;
1✔
31
        params.end_position = 0.8f;
1✔
32
        params.method = SubsegmentExtractionMethod::Direct;
1✔
33
        params.preserve_original_spacing = true;
1✔
34

35
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
36
        REQUIRE(result_subsegments != nullptr);
1✔
37
        
38
        auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(100));
1✔
39
        REQUIRE(subsegments.size() == 1);
1✔
40
        
41
        // Should include points from 20% to 80% of the line
42
        auto const & subsegment = subsegments[0];
1✔
43
        REQUIRE(subsegment.size() >= 2); // At least start and end points
1✔
44
        
45
        // Check that we have points within the specified range
46
        // With preserve_original_spacing=true, we get original points within the range
47
        // plus interpolated start/end points if needed
48
        // For a line from (0,0) to (4,4), 20% to 80% covers distance 0.8 to 3.2
49
        // This includes original points at positions 1, 2, 3
50
        
51
        // First point should be the first original point within range (position 1.0)
52
        REQUIRE_THAT(subsegment[0].x, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
53
        REQUIRE_THAT(subsegment[0].y, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
54
        
55
        // Last point should be the last original point within range (position 3.0)
56
        REQUIRE_THAT(subsegment.back().x, Catch::Matchers::WithinAbs(3.0f, 0.001f));
1✔
57
        REQUIRE_THAT(subsegment.back().y, Catch::Matchers::WithinAbs(3.0f, 0.001f));
1✔
58

59
        progress_val = -1;
1✔
60
        call_count = 0;
1✔
61
        result_subsegments = extract_line_subsegment(line_data.get(), params, cb);
1✔
62
        REQUIRE(result_subsegments != nullptr);
1✔
63
        REQUIRE(progress_val == 100);
1✔
64
        REQUIRE(call_count >= 1); // At least one call
1✔
65
    }
6✔
66

67
    SECTION("Direct method - full line") {
5✔
68
        line_data = std::make_shared<LineData>();
1✔
69
        std::vector<float> x_coords = {0.0f, 1.0f, 2.0f, 3.0f};
3✔
70
        std::vector<float> y_coords = {0.0f, 1.0f, 2.0f, 3.0f};
3✔
71
        line_data->addAtTime(TimeFrameIndex(200), x_coords, y_coords);
1✔
72
        
73
        params.start_position = 0.0f;
1✔
74
        params.end_position = 1.0f;
1✔
75
        params.method = SubsegmentExtractionMethod::Direct;
1✔
76
        params.preserve_original_spacing = true;
1✔
77

78
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
79
        REQUIRE(result_subsegments != nullptr);
1✔
80
        
81
        auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(200));
1✔
82
        REQUIRE(subsegments.size() == 1);
1✔
83
        
84
        auto const & subsegment = subsegments[0];
1✔
85
        REQUIRE(subsegment.size() == 4); // All original points
1✔
86
        
87
        // Should match original line exactly
88
        REQUIRE_THAT(subsegment[0].x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
89
        REQUIRE_THAT(subsegment[0].y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
90
        REQUIRE_THAT(subsegment[3].x, Catch::Matchers::WithinAbs(3.0f, 0.001f));
1✔
91
        REQUIRE_THAT(subsegment[3].y, Catch::Matchers::WithinAbs(3.0f, 0.001f));
1✔
92
    }
6✔
93

94
    SECTION("Parametric method - middle subsegment") {
5✔
95
        line_data = std::make_shared<LineData>();
1✔
96
        std::vector<float> x_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
97
        std::vector<float> y_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
98
        line_data->addAtTime(TimeFrameIndex(300), x_coords, y_coords);
1✔
99
        
100
        params.start_position = 0.3f;
1✔
101
        params.end_position = 0.7f;
1✔
102
        params.method = SubsegmentExtractionMethod::Parametric;
1✔
103
        params.polynomial_order = 3;
1✔
104
        params.output_points = 20;
1✔
105

106
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
107
        REQUIRE(result_subsegments != nullptr);
1✔
108
        
109
        auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(300));
1✔
110
        REQUIRE(subsegments.size() == 1);
1✔
111
        
112
        auto const & subsegment = subsegments[0];
1✔
113
        REQUIRE(subsegment.size() == 20); // Exactly output_points
1✔
114
        
115
        // Check that the subsegment is within the specified range
116
        // Parametric method generates exactly output_points with smooth interpolation
117
        float total_length = 4.0f;
1✔
118
        float start_distance = 0.3f * total_length; // 1.2
1✔
119
        float end_distance = 0.7f * total_length;   // 2.8
1✔
120
        
121
        // First point should be at start_distance
122
        REQUIRE_THAT(subsegment[0].x, Catch::Matchers::WithinAbs(1.2f, 0.1f));
1✔
123
        REQUIRE_THAT(subsegment[0].y, Catch::Matchers::WithinAbs(1.2f, 0.1f));
1✔
124
        
125
        // Last point should be at end_distance
126
        REQUIRE_THAT(subsegment.back().x, Catch::Matchers::WithinAbs(2.8f, 0.1f));
1✔
127
        REQUIRE_THAT(subsegment.back().y, Catch::Matchers::WithinAbs(2.8f, 0.1f));
1✔
128
    }
6✔
129

130
    SECTION("Parametric method - small subsegment") {
5✔
131
        line_data = std::make_shared<LineData>();
1✔
132
        std::vector<float> x_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
133
        std::vector<float> y_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
134
        line_data->addAtTime(TimeFrameIndex(400), x_coords, y_coords);
1✔
135
        
136
        params.start_position = 0.45f;
1✔
137
        params.end_position = 0.55f;
1✔
138
        params.method = SubsegmentExtractionMethod::Parametric;
1✔
139
        params.polynomial_order = 2;
1✔
140
        params.output_points = 10;
1✔
141

142
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
143
        REQUIRE(result_subsegments != nullptr);
1✔
144
        
145
        auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(400));
1✔
146
        REQUIRE(subsegments.size() == 1);
1✔
147
        
148
        auto const & subsegment = subsegments[0];
1✔
149
        REQUIRE(subsegment.size() == 10);
1✔
150
        
151
        // Check that the subsegment is within the specified range
152
        // Parametric method generates exactly output_points with smooth interpolation
153
        float total_length = 4.0f;
1✔
154
        float start_distance = 0.45f * total_length; // 1.8
1✔
155
        float end_distance = 0.55f * total_length;   // 2.2
1✔
156
        
157
        // First point should be at start_distance
158
        REQUIRE_THAT(subsegment[0].x, Catch::Matchers::WithinAbs(1.8f, 0.1f));
1✔
159
        REQUIRE_THAT(subsegment[0].y, Catch::Matchers::WithinAbs(1.8f, 0.1f));
1✔
160
        
161
        // Last point should be at end_distance
162
        REQUIRE_THAT(subsegment.back().x, Catch::Matchers::WithinAbs(2.2f, 0.1f));
1✔
163
        REQUIRE_THAT(subsegment.back().y, Catch::Matchers::WithinAbs(2.2f, 0.1f));
1✔
164
    }
6✔
165

166
    SECTION("Progress callback detailed check") {
5✔
167
        line_data = std::make_shared<LineData>();
1✔
168
        std::vector<float> x_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
169
        std::vector<float> y_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
170
        line_data->addAtTime(TimeFrameIndex(500), x_coords, y_coords);
1✔
171
        
172
        params.start_position = 0.2f;
1✔
173
        params.end_position = 0.8f;
1✔
174
        params.method = SubsegmentExtractionMethod::Direct;
1✔
175
        params.preserve_original_spacing = true;
1✔
176

177
        progress_val = 0;
1✔
178
        call_count = 0;
1✔
179
        std::vector<int> progress_values_seen;
1✔
180
        ProgressCallback detailed_cb = [&](int p) {
2✔
181
            progress_val = p;
3✔
182
            call_count = call_count + 1;
3✔
183
            progress_values_seen.push_back(p);
3✔
184
        };
1✔
185

186
        result_subsegments = extract_line_subsegment(line_data.get(), params, detailed_cb);
1✔
187
        REQUIRE(progress_val == 100);
1✔
188
        REQUIRE(call_count >= 2); // At least 0 and 100
1✔
189

190
        // Check that we see progress values
191
        REQUIRE(!progress_values_seen.empty());
1✔
192
        REQUIRE(progress_values_seen.back() == 100);
1✔
193
    }
6✔
194
}
10✔
195

196
TEST_CASE("Data Transform: Extract Line Subsegment - Error and Edge Cases", "[transforms][line_subsegment]") {
7✔
197
    std::shared_ptr<LineData> line_data;
7✔
198
    std::shared_ptr<LineData> result_subsegments;
7✔
199
    LineSubsegmentParameters params;
7✔
200
    volatile int progress_val = -1;
7✔
201
    volatile int call_count = 0;
7✔
202
    ProgressCallback cb = [&](int p) {
14✔
203
        progress_val = p;
2✔
204
        call_count = call_count + 1;
2✔
205
    };
7✔
206

207
    SECTION("Null input LineData") {
7✔
208
        line_data = nullptr; // Deliberately null
1✔
209
        params.start_position = 0.2f;
1✔
210
        params.end_position = 0.8f;
1✔
211
        params.method = SubsegmentExtractionMethod::Direct;
1✔
212

213
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
214
        REQUIRE(result_subsegments != nullptr);
1✔
215
        REQUIRE(result_subsegments->getTimesWithData().empty());
1✔
216

217
        progress_val = -1;
1✔
218
        call_count = 0;
1✔
219
        result_subsegments = extract_line_subsegment(line_data.get(), params, cb);
1✔
220
        REQUIRE(result_subsegments != nullptr);
1✔
221
        REQUIRE(result_subsegments->getTimesWithData().empty());
1✔
222
        REQUIRE(progress_val == 100);
1✔
223
        REQUIRE(call_count == 1); // Called once with 100
1✔
224
    }
7✔
225

226
    SECTION("Empty LineData (no lines)") {
7✔
227
        line_data = std::make_shared<LineData>();
1✔
228
        params.start_position = 0.2f;
1✔
229
        params.end_position = 0.8f;
1✔
230
        params.method = SubsegmentExtractionMethod::Direct;
1✔
231

232
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
233
        REQUIRE(result_subsegments != nullptr);
1✔
234
        REQUIRE(result_subsegments->getTimesWithData().empty());
1✔
235

236
        progress_val = -1;
1✔
237
        call_count = 0;
1✔
238
        result_subsegments = extract_line_subsegment(line_data.get(), params, cb);
1✔
239
        REQUIRE(result_subsegments != nullptr);
1✔
240
        REQUIRE(result_subsegments->getTimesWithData().empty());
1✔
241
        REQUIRE(progress_val == 100);
1✔
242
        REQUIRE(call_count == 1); // Called once with 100
1✔
243
    }
7✔
244

245
    SECTION("Single point line") {
7✔
246
        line_data = std::make_shared<LineData>();
1✔
247
        std::vector<float> x_coords = {1.0f};
3✔
248
        std::vector<float> y_coords = {2.0f};
3✔
249
        line_data->addAtTime(TimeFrameIndex(100), x_coords, y_coords);
1✔
250
        
251
        params.start_position = 0.2f;
1✔
252
        params.end_position = 0.8f;
1✔
253
        params.method = SubsegmentExtractionMethod::Direct;
1✔
254

255
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
256
        REQUIRE(result_subsegments != nullptr);
1✔
257
        
258
        auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(100));
1✔
259
        REQUIRE(subsegments.size() == 1);
1✔
260
        REQUIRE(subsegments[0].size() == 1); // Single point
1✔
261
        REQUIRE_THAT(subsegments[0][0].x, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
262
        REQUIRE_THAT(subsegments[0][0].y, Catch::Matchers::WithinAbs(2.0f, 0.001f));
1✔
263
    }
8✔
264

265
    SECTION("Invalid position range (start >= end)") {
7✔
266
        line_data = std::make_shared<LineData>();
1✔
267
        std::vector<float> x_coords = {0.0f, 1.0f, 2.0f, 3.0f};
3✔
268
        std::vector<float> y_coords = {0.0f, 1.0f, 2.0f, 3.0f};
3✔
269
        line_data->addAtTime(TimeFrameIndex(100), x_coords, y_coords);
1✔
270
        
271
        params.start_position = 0.8f;
1✔
272
        params.end_position = 0.2f; // Invalid: start > end
1✔
273
        params.method = SubsegmentExtractionMethod::Direct;
1✔
274

275
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
276
        REQUIRE(result_subsegments != nullptr);
1✔
277
        
278
        auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(100));
1✔
279
        REQUIRE(subsegments.empty()); // No valid subsegment
1✔
280
    }
8✔
281

282
    SECTION("Position values clamped to valid range") {
7✔
283
        line_data = std::make_shared<LineData>();
1✔
284
        std::vector<float> x_coords = {0.0f, 1.0f, 2.0f, 3.0f};
3✔
285
        std::vector<float> y_coords = {0.0f, 1.0f, 2.0f, 3.0f};
3✔
286
        line_data->addAtTime(TimeFrameIndex(100), x_coords, y_coords);
1✔
287
        
288
        params.start_position = -0.5f; // Invalid: negative
1✔
289
        params.end_position = 1.5f;    // Invalid: > 1.0
1✔
290
        params.method = SubsegmentExtractionMethod::Direct;
1✔
291

292
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
293
        REQUIRE(result_subsegments != nullptr);
1✔
294
        
295
        auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(100));
1✔
296
        REQUIRE(subsegments.size() == 1);
1✔
297
        
298
        // Should be clamped to [0.0, 1.0] range
299
        auto const & subsegment = subsegments[0];
1✔
300
        REQUIRE(subsegment.size() >= 2);
1✔
301
        REQUIRE_THAT(subsegment[0].x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
302
        REQUIRE_THAT(subsegment[0].y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
303
        REQUIRE_THAT(subsegment.back().x, Catch::Matchers::WithinAbs(3.0f, 0.001f));
1✔
304
        REQUIRE_THAT(subsegment.back().y, Catch::Matchers::WithinAbs(3.0f, 0.001f));
1✔
305
    }
8✔
306

307
    SECTION("Parametric method with insufficient points") {
7✔
308
        line_data = std::make_shared<LineData>();
1✔
309
        std::vector<float> x_coords = {0.0f, 1.0f}; // Only 2 points
3✔
310
        std::vector<float> y_coords = {0.0f, 1.0f};
3✔
311
        line_data->addAtTime(TimeFrameIndex(100), x_coords, y_coords);
1✔
312
        
313
        params.start_position = 0.2f;
1✔
314
        params.end_position = 0.8f;
1✔
315
        params.method = SubsegmentExtractionMethod::Parametric;
1✔
316
        params.polynomial_order = 3; // Requires at least 4 points
1✔
317

318
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
319
        REQUIRE(result_subsegments != nullptr);
1✔
320
        
321
        auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(100));
1✔
322
        REQUIRE(subsegments.size() == 1);
1✔
323
        
324
        // Should fall back to direct method
325
        auto const & subsegment = subsegments[0];
1✔
326
        REQUIRE(subsegment.size() >= 1);
1✔
327
    }
8✔
328

329
    SECTION("Zero-length line") {
7✔
330
        line_data = std::make_shared<LineData>();
1✔
331
        std::vector<float> x_coords = {1.0f, 1.0f, 1.0f}; // All same point
3✔
332
        std::vector<float> y_coords = {2.0f, 2.0f, 2.0f};
3✔
333
        line_data->addAtTime(TimeFrameIndex(100), x_coords, y_coords);
1✔
334
        
335
        params.start_position = 0.2f;
1✔
336
        params.end_position = 0.8f;
1✔
337
        params.method = SubsegmentExtractionMethod::Direct;
1✔
338

339
        result_subsegments = extract_line_subsegment(line_data.get(), params);
1✔
340
        REQUIRE(result_subsegments != nullptr);
1✔
341
        
342
        auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(100));
1✔
343
        REQUIRE(subsegments.size() == 1);
1✔
344
        REQUIRE(subsegments[0].size() == 1); // Single point
1✔
345
        REQUIRE_THAT(subsegments[0][0].x, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
346
        REQUIRE_THAT(subsegments[0][0].y, Catch::Matchers::WithinAbs(2.0f, 0.001f));
1✔
347
    }
8✔
348
}
14✔
349

350
#include "DataManager.hpp"
351
#include "IO/LoaderRegistry.hpp"
352
#include "transforms/TransformPipeline.hpp"
353
#include "transforms/TransformRegistry.hpp"
354

355
#include <filesystem>
356
#include <fstream>
357
#include <iostream>
358

359
TEST_CASE("Data Transform: Extract Line Subsegment - JSON pipeline", "[transforms][line_subsegment][json]") {
1✔
360
    const nlohmann::json json_config = {
1✔
361
        {"steps", {{
362
            {"step_id", "subsegment_step_1"},
363
            {"transform_name", "Extract Line Subsegment"},
364
            {"input_key", "TestLine.line1"},
365
            {"output_key", "ExtractedSubsegments"},
366
            {"parameters", {
367
                {"start_position", 0.2},
1✔
368
                {"end_position", 0.8},
1✔
369
                {"method", "Direct"},
370
                {"preserve_original_spacing", true}
371
            }}
372
        }}}
373
    };
46✔
374

375
    DataManager dm;
1✔
376
    TransformRegistry registry;
1✔
377

378
    auto time_frame = std::make_shared<TimeFrame>();
1✔
379
    dm.setTime(TimeKey("default"), time_frame);
1✔
380

381
    // Create test line data
382
    auto line_data = std::make_shared<LineData>();
1✔
383
    std::vector<float> x_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
384
    std::vector<float> y_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
385
    line_data->addAtTime(TimeFrameIndex(100), x_coords, y_coords);
1✔
386
    line_data->setTimeFrame(time_frame);
1✔
387
    dm.setData("TestLine.line1", line_data, TimeKey("default"));
3✔
388

389
    TransformPipeline pipeline(&dm, &registry);
1✔
390
    pipeline.loadFromJson(json_config);
1✔
391
    pipeline.execute();
1✔
392

393
    // Verify the results
394
    auto result_subsegments = dm.getData<LineData>("ExtractedSubsegments");
3✔
395
    REQUIRE(result_subsegments != nullptr);
1✔
396

397
    auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(100));
1✔
398
    REQUIRE(subsegments.size() == 1);
1✔
399
    
400
    auto const & subsegment = subsegments[0];
1✔
401
    REQUIRE(subsegment.size() >= 2);
1✔
402
    
403
    // Check that the subsegment is within the specified range
404
    // With preserve_original_spacing=true, we get original points within the range
405
    float total_length = 4.0f;
1✔
406
    float start_distance = 0.2f * total_length; // 0.8
1✔
407
    float end_distance = 0.8f * total_length;   // 3.2
1✔
408
    
409
    // First point should be the first original point within range (position 1.0)
410
    REQUIRE_THAT(subsegment[0].x, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
411
    REQUIRE_THAT(subsegment[0].y, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
412
    // Last point should be the last original point within range (position 3.0)
413
    REQUIRE_THAT(subsegment.back().x, Catch::Matchers::WithinAbs(3.0f, 0.001f));
1✔
414
    REQUIRE_THAT(subsegment.back().y, Catch::Matchers::WithinAbs(3.0f, 0.001f));
1✔
415
}
48✔
416

417
#include "transforms/ParameterFactory.hpp"
418
#include "transforms/TransformRegistry.hpp"
419

420
TEST_CASE("Data Transform: Extract Line Subsegment - Parameter Factory", "[transforms][line_subsegment][factory]") {
1✔
421
    auto& factory = ParameterFactory::getInstance();
1✔
422
    factory.initializeDefaultSetters();
1✔
423

424
    auto params_base = std::make_unique<LineSubsegmentParameters>();
1✔
425
    REQUIRE(params_base != nullptr);
1✔
426

427
    const nlohmann::json params_json = {
1✔
428
        {"start_position", 0.3},
1✔
429
        {"end_position", 0.7},
1✔
430
        {"method", "Parametric"},
431
        {"polynomial_order", 4},
1✔
432
        {"output_points", 100},
1✔
433
        {"preserve_original_spacing", false}
434
    };
26✔
435

436
    for (auto const& [key, val] : params_json.items()) {
7✔
437
        factory.setParameter("Extract Line Subsegment", params_base.get(), key, val, nullptr);
18✔
438
    }
1✔
439

440
    auto* params = dynamic_cast<LineSubsegmentParameters*>(params_base.get());
1✔
441
    REQUIRE(params != nullptr);
1✔
442

443
    REQUIRE(params->start_position == 0.3f);
1✔
444
    REQUIRE(params->end_position == 0.7f);
1✔
445
    REQUIRE(params->method == SubsegmentExtractionMethod::Parametric);
1✔
446
    REQUIRE(params->polynomial_order == 4);
1✔
447
    REQUIRE(params->output_points == 100);
1✔
448
    REQUIRE(params->preserve_original_spacing == false);
1✔
449
}
25✔
450

451
TEST_CASE("Data Transform: Extract Line Subsegment - load_data_from_json_config", "[transforms][line_subsegment][json_config]") {
1✔
452
    // Create DataManager and populate it with LineData in code
453
    DataManager dm;
1✔
454

455
    // Create a TimeFrame for our data
456
    auto time_frame = std::make_shared<TimeFrame>();
1✔
457
    dm.setTime(TimeKey("default"), time_frame);
1✔
458
    
459
    // Create test line data in code
460
    auto test_line = std::make_shared<LineData>();
1✔
461
    std::vector<float> x_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
462
    std::vector<float> y_coords = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
3✔
463
    test_line->addAtTime(TimeFrameIndex(100), x_coords, y_coords);
1✔
464
    test_line->setTimeFrame(time_frame);
1✔
465
    
466
    // Store the line data in DataManager with a known key
467
    dm.setData("test_line", test_line, TimeKey("default"));
3✔
468
    
469
    // Create JSON configuration for transformation pipeline using unified format
470
    const char* json_config = 
1✔
471
        "[\n"
472
        "{\n"
473
        "    \"transformations\": {\n"
474
        "        \"metadata\": {\n"
475
        "            \"name\": \"Line Subsegment Extraction Pipeline\",\n"
476
        "            \"description\": \"Test line subsegment extraction\",\n"
477
        "            \"version\": \"1.0\"\n"
478
        "        },\n"
479
        "        \"steps\": [\n"
480
        "            {\n"
481
        "                \"step_id\": \"1\",\n"
482
        "                \"transform_name\": \"Extract Line Subsegment\",\n"
483
        "                \"phase\": \"analysis\",\n"
484
        "                \"input_key\": \"test_line\",\n"
485
        "                \"output_key\": \"extracted_subsegments\",\n"
486
        "                \"parameters\": {\n"
487
        "                    \"start_position\": 0.2,\n"
488
        "                    \"end_position\": 0.8,\n"
489
        "                    \"method\": \"Direct\",\n"
490
        "                    \"preserve_original_spacing\": true\n"
491
        "                }\n"
492
        "            }\n"
493
        "        ]\n"
494
        "    }\n"
495
        "}\n"
496
        "]";
497
    
498
    // Create temporary directory and write JSON config to file
499
    std::filesystem::path test_dir = std::filesystem::temp_directory_path() / "line_subsegment_pipeline_test";
1✔
500
    std::filesystem::create_directories(test_dir);
1✔
501
    
502
    std::filesystem::path json_filepath = test_dir / "pipeline_config.json";
1✔
503
    {
504
        std::ofstream json_file(json_filepath);
1✔
505
        REQUIRE(json_file.is_open());
1✔
506
        json_file << json_config;
1✔
507
        json_file.close();
1✔
508
    }
1✔
509
    
510
    // Execute the transformation pipeline using load_data_from_json_config
511
    auto data_info_list = load_data_from_json_config(&dm, json_filepath.string());
1✔
512
    
513
    // Verify the transformation was executed and results are available
514
    auto result_subsegments = dm.getData<LineData>("extracted_subsegments");
3✔
515
    REQUIRE(result_subsegments != nullptr);
1✔
516
    
517
    // Verify the subsegment extraction results
518
    auto const & subsegments = result_subsegments->getAtTime(TimeFrameIndex(100));
1✔
519
    REQUIRE(subsegments.size() == 1);
1✔
520
    
521
    auto const & subsegment = subsegments[0];
1✔
522
    REQUIRE(subsegment.size() >= 2);
1✔
523
    
524
    // Check that the subsegment is within the specified range
525
    // With preserve_original_spacing=true, we get original points within the range
526
    float total_length = 4.0f;
1✔
527
    float start_distance = 0.2f * total_length; // 0.8
1✔
528
    float end_distance = 0.8f * total_length;   // 3.2
1✔
529
    
530
    // First point should be the first original point within range (position 1.0)
531
    REQUIRE_THAT(subsegment[0].x, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
532
    REQUIRE_THAT(subsegment[0].y, Catch::Matchers::WithinAbs(1.0f, 0.001f));
1✔
533
    // Last point should be the last original point within range (position 3.0)
534
    REQUIRE_THAT(subsegment.back().x, Catch::Matchers::WithinAbs(3.0f, 0.001f));
1✔
535
    REQUIRE_THAT(subsegment.back().y, Catch::Matchers::WithinAbs(3.0f, 0.001f));
1✔
536
    
537
    // Test another pipeline with different parameters (parametric method)
538
    const char* json_config_parametric = 
1✔
539
        "[\n"
540
        "{\n"
541
        "    \"transformations\": {\n"
542
        "        \"metadata\": {\n"
543
        "            \"name\": \"Line Subsegment Extraction with Parametric Method\",\n"
544
        "            \"description\": \"Test line subsegment extraction with parametric interpolation\",\n"
545
        "            \"version\": \"1.0\"\n"
546
        "        },\n"
547
        "        \"steps\": [\n"
548
        "            {\n"
549
        "                \"step_id\": \"1\",\n"
550
        "                \"transform_name\": \"Extract Line Subsegment\",\n"
551
        "                \"phase\": \"analysis\",\n"
552
        "                \"input_key\": \"test_line\",\n"
553
        "                \"output_key\": \"extracted_subsegments_parametric\",\n"
554
        "                \"parameters\": {\n"
555
        "                    \"start_position\": 0.3,\n"
556
        "                    \"end_position\": 0.7,\n"
557
        "                    \"method\": \"Parametric\",\n"
558
        "                    \"polynomial_order\": 3,\n"
559
        "                    \"output_points\": 50\n"
560
        "                }\n"
561
        "            }\n"
562
        "        ]\n"
563
        "    }\n"
564
        "}\n"
565
        "]";
566
    
567
    std::filesystem::path json_filepath_parametric = test_dir / "pipeline_config_parametric.json";
1✔
568
    {
569
        std::ofstream json_file(json_filepath_parametric);
1✔
570
        REQUIRE(json_file.is_open());
1✔
571
        json_file << json_config_parametric;
1✔
572
        json_file.close();
1✔
573
    }
1✔
574
    
575
    // Execute the parametric pipeline
576
    auto data_info_list_parametric = load_data_from_json_config(&dm, json_filepath_parametric.string());
1✔
577
    
578
    // Verify the parametric results
579
    auto result_subsegments_parametric = dm.getData<LineData>("extracted_subsegments_parametric");
3✔
580
    REQUIRE(result_subsegments_parametric != nullptr);
1✔
581
    
582
    auto const & subsegments_parametric = result_subsegments_parametric->getAtTime(TimeFrameIndex(100));
1✔
583
    REQUIRE(subsegments_parametric.size() == 1);
1✔
584
    
585
    auto const & subsegment_parametric = subsegments_parametric[0];
1✔
586
    REQUIRE(subsegment_parametric.size() == 50); // Exactly output_points
1✔
587
    
588
    // Check that the parametric subsegment is within the specified range
589
    float start_distance_parametric = 0.3f * total_length; // 1.2
1✔
590
    float end_distance_parametric = 0.7f * total_length;   // 2.8
1✔
591
    
592
    REQUIRE_THAT(subsegment_parametric[0].x, Catch::Matchers::WithinAbs(1.2f, 0.1f));
1✔
593
    REQUIRE_THAT(subsegment_parametric[0].y, Catch::Matchers::WithinAbs(1.2f, 0.1f));
1✔
594
    REQUIRE_THAT(subsegment_parametric.back().x, Catch::Matchers::WithinAbs(2.8f, 0.1f));
1✔
595
    REQUIRE_THAT(subsegment_parametric.back().y, Catch::Matchers::WithinAbs(2.8f, 0.1f));
1✔
596
    
597
    // Test full line extraction
598
    const char* json_config_full = 
1✔
599
        "[\n"
600
        "{\n"
601
        "    \"transformations\": {\n"
602
        "        \"metadata\": {\n"
603
        "            \"name\": \"Line Subsegment Full Line Extraction\",\n"
604
        "            \"description\": \"Test full line extraction\",\n"
605
        "            \"version\": \"1.0\"\n"
606
        "        },\n"
607
        "        \"steps\": [\n"
608
        "            {\n"
609
        "                \"step_id\": \"1\",\n"
610
        "                \"transform_name\": \"Extract Line Subsegment\",\n"
611
        "                \"phase\": \"analysis\",\n"
612
        "                \"input_key\": \"test_line\",\n"
613
        "                \"output_key\": \"extracted_subsegments_full\",\n"
614
        "                \"parameters\": {\n"
615
        "                    \"start_position\": 0.0,\n"
616
        "                    \"end_position\": 1.0,\n"
617
        "                    \"method\": \"Direct\",\n"
618
        "                    \"preserve_original_spacing\": true\n"
619
        "                }\n"
620
        "            }\n"
621
        "        ]\n"
622
        "    }\n"
623
        "}\n"
624
        "]";
625
    
626
    std::filesystem::path json_filepath_full = test_dir / "pipeline_config_full.json";
1✔
627
    {
628
        std::ofstream json_file(json_filepath_full);
1✔
629
        REQUIRE(json_file.is_open());
1✔
630
        json_file << json_config_full;
1✔
631
        json_file.close();
1✔
632
    }
1✔
633
    
634
    // Execute the full line pipeline
635
    auto data_info_list_full = load_data_from_json_config(&dm, json_filepath_full.string());
1✔
636
    
637
    // Verify the full line results
638
    auto result_subsegments_full = dm.getData<LineData>("extracted_subsegments_full");
3✔
639
    REQUIRE(result_subsegments_full != nullptr);
1✔
640
    
641
    auto const & subsegments_full = result_subsegments_full->getAtTime(TimeFrameIndex(100));
1✔
642
    REQUIRE(subsegments_full.size() == 1);
1✔
643
    
644
    auto const & subsegment_full = subsegments_full[0];
1✔
645
    REQUIRE(subsegment_full.size() == 5); // All original points
1✔
646
    
647
    // Should match original line exactly
648
    REQUIRE_THAT(subsegment_full[0].x, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
649
    REQUIRE_THAT(subsegment_full[0].y, Catch::Matchers::WithinAbs(0.0f, 0.001f));
1✔
650
    REQUIRE_THAT(subsegment_full.back().x, Catch::Matchers::WithinAbs(4.0f, 0.001f));
1✔
651
    REQUIRE_THAT(subsegment_full.back().y, Catch::Matchers::WithinAbs(4.0f, 0.001f));
1✔
652
    
653
    // Cleanup
654
    try {
655
        std::filesystem::remove_all(test_dir);
1✔
NEW
656
    } catch (const std::exception& e) {
×
NEW
657
        std::cerr << "Warning: Cleanup failed: " << e.what() << std::endl;
×
NEW
658
    }
×
659
}
2✔
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