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

paulmthompson / WhiskerToolbox / 17344483100

30 Aug 2025 01:39PM UTC coverage: 67.624% (+0.5%) from 67.086%
17344483100

push

github

paulmthompson
added line resample testing

290 of 306 new or added lines in 2 files covered. (94.77%)

27817 of 41135 relevant lines covered (67.62%)

1101.94 hits per line

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

97.7
/src/DataManager/transforms/Lines/Line_Resample/line_resample.test.cpp
1
#include "catch2/catch_test_macros.hpp"
2
#include "catch2/matchers/catch_matchers_vector.hpp"
3

4
#include "Lines/Line_Data.hpp"
5
#include "transforms/Lines/Line_Resample/line_resample.hpp"
6
#include "transforms/data_transforms.hpp"// For ProgressCallback
7

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

12
// Using Catch::Matchers::Equals for vectors of floats.
13

14
TEST_CASE("Data Transform: Line Resample - Happy Path", "[transforms][line_resample]") {
4✔
15
    std::shared_ptr<LineData> line_data;
4✔
16
    std::shared_ptr<LineData> result_data;
4✔
17
    LineResampleParameters params;
4✔
18
    int volatile progress_val = -1;// Volatile to prevent optimization issues in test
4✔
19
    int volatile call_count = 0;   // Volatile for the same reason
4✔
20
    ProgressCallback cb = [&](int p) {
8✔
21
        progress_val = p;
4✔
22
        call_count = call_count + 1;
4✔
23
    };
4✔
24

25
    SECTION("FixedSpacing algorithm") {
4✔
26
        // Create test line data with multiple lines at different times
27
        line_data = std::make_shared<LineData>();
1✔
28
        line_data->setImageSize(ImageSize(1000, 1000));
1✔
29

30
        // Add a simple line at time 100
31
        std::vector<float> x1 = {10.0f, 20.0f, 30.0f, 40.0f, 50.0f};
3✔
32
        std::vector<float> y1 = {10.0f, 20.0f, 30.0f, 40.0f, 50.0f};
3✔
33
        line_data->addAtTime(TimeFrameIndex(100), x1, y1);
1✔
34

35
        // Add another line at time 200
36
        std::vector<float> x2 = {100.0f, 110.0f, 120.0f, 130.0f, 140.0f, 150.0f};
3✔
37
        std::vector<float> y2 = {100.0f, 110.0f, 120.0f, 130.0f, 140.0f, 150.0f};
3✔
38
        line_data->addAtTime(TimeFrameIndex(200), x2, y2);
1✔
39

40
        params.algorithm = LineSimplificationAlgorithm::FixedSpacing;
1✔
41
        params.target_spacing = 15.0f;
1✔
42
        params.epsilon = 2.0f;
1✔
43

44
        result_data = line_resample(line_data.get(), params);
1✔
45
        REQUIRE(result_data != nullptr);
1✔
46
        REQUIRE(result_data->getImageSize() == line_data->getImageSize());
1✔
47

48
        // Check that we have data at both times
49
        auto times_with_data = result_data->getTimesWithData();
1✔
50
        std::vector<TimeFrameIndex> expected_times = {TimeFrameIndex(100), TimeFrameIndex(200)};
3✔
51
        std::vector<TimeFrameIndex> actual_times(times_with_data.begin(), times_with_data.end());
3✔
52
        REQUIRE_THAT(actual_times, Catch::Matchers::Equals(expected_times));
1✔
53

54
        progress_val = -1;
1✔
55
        call_count = 0;
1✔
56
        result_data = line_resample(line_data.get(), params, cb);
1✔
57
        REQUIRE(result_data != nullptr);
1✔
58
        REQUIRE(progress_val == 100);
1✔
59
        REQUIRE(call_count > 0);
1✔
60
    }
5✔
61

62
    SECTION("DouglasPeucker algorithm") {
4✔
63
        // Create test line data with a complex line
64
        line_data = std::make_shared<LineData>();
1✔
65
        line_data->setImageSize(ImageSize(1000, 1000));
1✔
66

67
        // Add a line with many points that can be simplified
68
        std::vector<float> x = {10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f, 18.0f, 19.0f, 20.0f};
3✔
69
        std::vector<float> y = {10.0f, 10.1f, 10.2f, 10.3f, 10.4f, 10.5f, 10.6f, 10.7f, 10.8f, 10.9f, 11.0f};
3✔
70
        line_data->addAtTime(TimeFrameIndex(100), x, y);
1✔
71

72
        params.algorithm = LineSimplificationAlgorithm::DouglasPeucker;
1✔
73
        params.target_spacing = 5.0f;
1✔
74
        params.epsilon = 0.5f;
1✔
75

76
        result_data = line_resample(line_data.get(), params);
1✔
77
        REQUIRE(result_data != nullptr);
1✔
78
        REQUIRE(result_data->getImageSize() == line_data->getImageSize());
1✔
79

80
        // Check that we have data at the time
81
        auto times_with_data = result_data->getTimesWithData();
1✔
82
        std::vector<TimeFrameIndex> expected_times = {TimeFrameIndex(100)};
3✔
83
        std::vector<TimeFrameIndex> actual_times(times_with_data.begin(), times_with_data.end());
3✔
84
        REQUIRE_THAT(actual_times, Catch::Matchers::Equals(expected_times));
1✔
85

86
        // The simplified line should have fewer points than the original
87
        auto original_lines = line_data->getAtTime(TimeFrameIndex(100));
1✔
88
        auto result_lines = result_data->getAtTime(TimeFrameIndex(100));
1✔
89
        REQUIRE(original_lines.size() == result_lines.size());     // Same number of lines
1✔
90
        REQUIRE(original_lines[0].size() > result_lines[0].size());// Fewer points in simplified line
1✔
91
    }
5✔
92

93
    SECTION("Empty line data") {
4✔
94
        line_data = std::make_shared<LineData>();
1✔
95
        line_data->setImageSize(ImageSize(1000, 1000));
1✔
96

97
        params.algorithm = LineSimplificationAlgorithm::FixedSpacing;
1✔
98
        params.target_spacing = 10.0f;
1✔
99
        params.epsilon = 2.0f;
1✔
100

101
        result_data = line_resample(line_data.get(), params);
1✔
102
        REQUIRE(result_data != nullptr);
1✔
103
        REQUIRE(result_data->getImageSize() == line_data->getImageSize());
1✔
104
        REQUIRE(result_data->getTimesWithData().empty());
1✔
105
    }
4✔
106

107
    SECTION("Line with empty lines") {
4✔
108
        line_data = std::make_shared<LineData>();
1✔
109
        line_data->setImageSize(ImageSize(1000, 1000));
1✔
110

111
        // Add a normal line
112
        std::vector<float> x1 = {10.0f, 20.0f, 30.0f};
3✔
113
        std::vector<float> y1 = {10.0f, 20.0f, 30.0f};
3✔
114
        line_data->addAtTime(TimeFrameIndex(100), x1, y1);
1✔
115

116
        // Add an empty line
117
        std::vector<float> x2 = {};
1✔
118
        std::vector<float> y2 = {};
1✔
119
        line_data->addAtTime(TimeFrameIndex(200), x2, y2);
1✔
120

121
        params.algorithm = LineSimplificationAlgorithm::FixedSpacing;
1✔
122
        params.target_spacing = 10.0f;
1✔
123
        params.epsilon = 2.0f;
1✔
124

125
        result_data = line_resample(line_data.get(), params);
1✔
126
        REQUIRE(result_data != nullptr);
1✔
127
        REQUIRE(result_data->getImageSize() == line_data->getImageSize());
1✔
128

129
        // Should have data at both times
130
        auto times_with_data = result_data->getTimesWithData();
1✔
131
        std::vector<TimeFrameIndex> expected_times = {TimeFrameIndex(100), TimeFrameIndex(200)};
3✔
132
        std::vector<TimeFrameIndex> actual_times(times_with_data.begin(), times_with_data.end());
3✔
133
        REQUIRE_THAT(actual_times, Catch::Matchers::Equals(expected_times));
1✔
134

135
        // Time 100 should have a non-empty line, time 200 should have an empty line
136
        auto lines_at_100 = result_data->getAtTime(TimeFrameIndex(100));
1✔
137
        auto lines_at_200 = result_data->getAtTime(TimeFrameIndex(200));
1✔
138
        REQUIRE(lines_at_100.size() == 1);
1✔
139
        REQUIRE(!lines_at_100[0].empty());
1✔
140
        REQUIRE(lines_at_200.size() == 1);
1✔
141
        REQUIRE(lines_at_200[0].empty());
1✔
142
    }
5✔
143
}
8✔
144

145
TEST_CASE("Data Transform: Line Resample - Error and Edge Cases", "[transforms][line_resample]") {
1✔
146
    std::shared_ptr<LineData> line_data;
1✔
147
    std::shared_ptr<LineData> result_data;
1✔
148
    LineResampleParameters params;
1✔
149
    int volatile progress_val = -1;
1✔
150
    int volatile call_count = 0;
1✔
151
    ProgressCallback cb = [&](int p) {
2✔
NEW
152
        progress_val = p;
×
NEW
153
        call_count = call_count + 1;
×
154
    };
1✔
155

156
    SECTION("Single point line") {
1✔
157
        line_data = std::make_shared<LineData>();
1✔
158
        line_data->setImageSize(ImageSize(1000, 1000));
1✔
159

160
        std::vector<float> x = {10.0f};
3✔
161
        std::vector<float> y = {10.0f};
3✔
162
        line_data->addAtTime(TimeFrameIndex(100), x, y);
1✔
163

164
        params.algorithm = LineSimplificationAlgorithm::FixedSpacing;
1✔
165
        params.target_spacing = 10.0f;
1✔
166
        params.epsilon = 2.0f;
1✔
167

168
        result_data = line_resample(line_data.get(), params);
1✔
169
        REQUIRE(result_data != nullptr);
1✔
170
        REQUIRE(result_data->getImageSize() == line_data->getImageSize());
1✔
171

172
        auto lines = result_data->getAtTime(TimeFrameIndex(100));
1✔
173
        REQUIRE(lines.size() == 1);
1✔
174
        REQUIRE(lines[0].size() == 1);// Should still have one point
1✔
175
    }
2✔
176
}
2✔
177

178
#include "DataManager.hpp"
179
#include "IO/LoaderRegistry.hpp"
180
#include "transforms/TransformPipeline.hpp"
181
#include "transforms/TransformRegistry.hpp"
182

183
#include <filesystem>
184
#include <fstream>
185
#include <iostream>
186

187
TEST_CASE("Data Transform: Line Resample - JSON pipeline", "[transforms][line_resample][json]") {
1✔
188
    nlohmann::json const json_config = {
1✔
189
            {"steps", {{{"step_id", "resample_step_1"}, {"transform_name", "Resample Line"}, {"input_key", "TestLines.channel1"}, {"output_key", "ResampledLines"}, {"parameters", {{"algorithm", "FixedSpacing"}, {"target_spacing", 15.0}, {"epsilon", 2.0}}}}}}};
44✔
190

191
    DataManager dm;
1✔
192
    TransformRegistry registry;
1✔
193

194
    auto time_frame = std::make_shared<TimeFrame>();
1✔
195
    dm.setTime(TimeKey("default"), time_frame);
1✔
196

197
    // Create test line data
198
    auto line_data = std::make_shared<LineData>();
1✔
199
    line_data->setImageSize(ImageSize(1000, 1000));
1✔
200

201
    // Add test lines at different times
202
    std::vector<float> x1 = {10.0f, 20.0f, 30.0f, 40.0f, 50.0f};
3✔
203
    std::vector<float> y1 = {10.0f, 20.0f, 30.0f, 40.0f, 50.0f};
3✔
204
    line_data->addAtTime(TimeFrameIndex(100), x1, y1);
1✔
205

206
    std::vector<float> x2 = {100.0f, 110.0f, 120.0f, 130.0f, 140.0f, 150.0f};
3✔
207
    std::vector<float> y2 = {100.0f, 110.0f, 120.0f, 130.0f, 140.0f, 150.0f};
3✔
208
    line_data->addAtTime(TimeFrameIndex(200), x2, y2);
1✔
209

210
    line_data->setTimeFrame(time_frame);
1✔
211
    dm.setData("TestLines.channel1", line_data, TimeKey("default"));
3✔
212

213
    TransformPipeline pipeline(&dm, &registry);
1✔
214
    pipeline.loadFromJson(json_config);
1✔
215
    pipeline.execute();
1✔
216

217
    // Verify the results
218
    auto result_line_data = dm.getData<LineData>("ResampledLines");
3✔
219
    REQUIRE(result_line_data != nullptr);
1✔
220
    REQUIRE(result_line_data->getImageSize() == line_data->getImageSize());
1✔
221

222
    // Check that we have data at both times
223
    auto times_with_data = result_line_data->getTimesWithData();
1✔
224
    std::vector<TimeFrameIndex> expected_times = {TimeFrameIndex(100), TimeFrameIndex(200)};
3✔
225
    std::vector<TimeFrameIndex> actual_times(times_with_data.begin(), times_with_data.end());
3✔
226
    REQUIRE_THAT(actual_times, Catch::Matchers::Equals(expected_times));
1✔
227
}
44✔
228

229
#include "transforms/ParameterFactory.hpp"
230
#include "transforms/TransformRegistry.hpp"
231

232
TEST_CASE("Data Transform: Line Resample - Parameter Factory", "[transforms][line_resample][factory]") {
1✔
233
    auto & factory = ParameterFactory::getInstance();
1✔
234
    factory.initializeDefaultSetters();
1✔
235

236
    auto params_base = std::make_unique<LineResampleParameters>();
1✔
237
    REQUIRE(params_base != nullptr);
1✔
238

239
    nlohmann::json const params_json = {
1✔
240
            {"algorithm", "Douglas-Peucker"},
241
            {"target_spacing", 25.0},
1✔
242
            {"epsilon", 5.0}};
15✔
243

244
    for (auto const & [key, val]: params_json.items()) {
4✔
245
        factory.setParameter("Resample Line", params_base.get(), key, val, nullptr);
9✔
246
    }
1✔
247

248
    auto * params = dynamic_cast<LineResampleParameters *>(params_base.get());
1✔
249
    REQUIRE(params != nullptr);
1✔
250

251
    REQUIRE(params->algorithm == LineSimplificationAlgorithm::DouglasPeucker);
1✔
252
    REQUIRE(params->target_spacing == 25.0f);
1✔
253
    REQUIRE(params->epsilon == 5.0f);
1✔
254
}
14✔
255

256
TEST_CASE("Data Transform: Line Resample - load_data_from_json_config", "[transforms][line_resample][json_config]") {
1✔
257
    // Create DataManager and populate it with LineData in code
258
    DataManager dm;
1✔
259

260
    // Create a TimeFrame for our data
261
    auto time_frame = std::make_shared<TimeFrame>();
1✔
262
    dm.setTime(TimeKey("default"), time_frame);
1✔
263

264
    // Create test line data in code
265
    auto test_lines = std::make_shared<LineData>();
1✔
266
    test_lines->setImageSize(ImageSize(1000, 1000));
1✔
267

268
    // Add test lines at different times
269
    std::vector<float> x1 = {10.0f, 20.0f, 30.0f, 40.0f, 50.0f};
3✔
270
    std::vector<float> y1 = {10.0f, 20.0f, 30.0f, 40.0f, 50.0f};
3✔
271
    test_lines->addAtTime(TimeFrameIndex(100), x1, y1);
1✔
272

273
    std::vector<float> x2 = {100.0f, 110.0f, 120.0f, 130.0f, 140.0f, 150.0f};
3✔
274
    std::vector<float> y2 = {100.0f, 110.0f, 120.0f, 130.0f, 140.0f, 150.0f};
3✔
275
    test_lines->addAtTime(TimeFrameIndex(200), x2, y2);
1✔
276

277
    test_lines->setTimeFrame(time_frame);
1✔
278

279
    // Store the line data in DataManager with a known key
280
    dm.setData("test_lines", test_lines, TimeKey("default"));
3✔
281

282
    // Create JSON configuration for transformation pipeline using unified format
283
    char const * json_config =
1✔
284
            "[\n"
285
            "{\n"
286
            "    \"transformations\": {\n"
287
            "        \"metadata\": {\n"
288
            "            \"name\": \"Line Resample Pipeline\",\n"
289
            "            \"description\": \"Test line resampling on line data\",\n"
290
            "            \"version\": \"1.0\"\n"
291
            "        },\n"
292
            "        \"steps\": [\n"
293
            "            {\n"
294
            "                \"step_id\": \"1\",\n"
295
            "                \"transform_name\": \"Resample Line\",\n"
296
            "                \"phase\": \"analysis\",\n"
297
            "                \"input_key\": \"test_lines\",\n"
298
            "                \"output_key\": \"resampled_lines\",\n"
299
            "                \"parameters\": {\n"
300
            "                    \"algorithm\": \"FixedSpacing\",\n"
301
            "                    \"target_spacing\": 15.0,\n"
302
            "                    \"epsilon\": 2.0\n"
303
            "                }\n"
304
            "            }\n"
305
            "        ]\n"
306
            "    }\n"
307
            "}\n"
308
            "]";
309

310
    // Create temporary directory and write JSON config to file
311
    std::filesystem::path test_dir = std::filesystem::temp_directory_path() / "line_resample_pipeline_test";
1✔
312
    std::filesystem::create_directories(test_dir);
1✔
313

314
    std::filesystem::path json_filepath = test_dir / "pipeline_config.json";
1✔
315
    {
316
        std::ofstream json_file(json_filepath);
1✔
317
        REQUIRE(json_file.is_open());
1✔
318
        json_file << json_config;
1✔
319
        json_file.close();
1✔
320
    }
1✔
321

322
    // Execute the transformation pipeline using load_data_from_json_config
323
    auto data_info_list = load_data_from_json_config(&dm, json_filepath.string());
1✔
324

325
    // Verify the transformation was executed and results are available
326
    auto result_line_data = dm.getData<LineData>("resampled_lines");
3✔
327
    REQUIRE(result_line_data != nullptr);
1✔
328
    REQUIRE(result_line_data->getImageSize() == test_lines->getImageSize());
1✔
329

330
    // Verify the resampling results
331
    auto times_with_data = result_line_data->getTimesWithData();
1✔
332
    std::vector<TimeFrameIndex> expected_times = {TimeFrameIndex(100), TimeFrameIndex(200)};
3✔
333
    std::vector<TimeFrameIndex> actual_times(times_with_data.begin(), times_with_data.end());
3✔
334
    REQUIRE_THAT(actual_times, Catch::Matchers::Equals(expected_times));
1✔
335

336
    // Test another pipeline with different parameters (Douglas-Peucker)
337
    char const * json_config_dp =
1✔
338
            "[\n"
339
            "{\n"
340
            "    \"transformations\": {\n"
341
            "        \"metadata\": {\n"
342
            "            \"name\": \"Line Resample with Douglas-Peucker\",\n"
343
            "            \"description\": \"Test line resampling with Douglas-Peucker algorithm\",\n"
344
            "            \"version\": \"1.0\"\n"
345
            "        },\n"
346
            "        \"steps\": [\n"
347
            "            {\n"
348
            "                \"step_id\": \"1\",\n"
349
            "                \"transform_name\": \"Resample Line\",\n"
350
            "                \"phase\": \"analysis\",\n"
351
            "                \"input_key\": \"test_lines\",\n"
352
            "                \"output_key\": \"resampled_lines_dp\",\n"
353
            "                \"parameters\": {\n"
354
            "                    \"algorithm\": \"Douglas-Peucker\",\n"
355
            "                    \"target_spacing\": 5.0,\n"
356
            "                    \"epsilon\": 3.0\n"
357
            "                }\n"
358
            "            }\n"
359
            "        ]\n"
360
            "    }\n"
361
            "}\n"
362
            "]";
363

364
    std::filesystem::path json_filepath_dp = test_dir / "pipeline_config_dp.json";
1✔
365
    {
366
        std::ofstream json_file(json_filepath_dp);
1✔
367
        REQUIRE(json_file.is_open());
1✔
368
        json_file << json_config_dp;
1✔
369
        json_file.close();
1✔
370
    }
1✔
371

372
    // Execute the Douglas-Peucker pipeline
373
    auto data_info_list_dp = load_data_from_json_config(&dm, json_filepath_dp.string());
1✔
374

375
    // Verify the Douglas-Peucker results
376
    auto result_line_data_dp = dm.getData<LineData>("resampled_lines_dp");
3✔
377
    REQUIRE(result_line_data_dp != nullptr);
1✔
378
    REQUIRE(result_line_data_dp->getImageSize() == test_lines->getImageSize());
1✔
379

380
    // Check that we have data at both times
381
    auto times_with_data_dp = result_line_data_dp->getTimesWithData();
1✔
382
    std::vector<TimeFrameIndex> actual_times_dp(times_with_data_dp.begin(), times_with_data_dp.end());
3✔
383
    REQUIRE_THAT(actual_times_dp, Catch::Matchers::Equals(expected_times));
1✔
384

385
    // Cleanup
386
    try {
387
        std::filesystem::remove_all(test_dir);
1✔
NEW
388
    } catch (std::exception const & e) {
×
NEW
389
        std::cerr << "Warning: Cleanup failed: " << e.what() << std::endl;
×
NEW
390
    }
×
391
}
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