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

paulmthompson / WhiskerToolbox / 17270491352

27 Aug 2025 02:57PM UTC coverage: 65.333%. Remained the same
17270491352

push

github

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

352 of 628 new or added lines in 92 files covered. (56.05%)

357 existing lines in 24 files now uncovered.

26429 of 40453 relevant lines covered (65.33%)

1119.34 hits per line

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

46.85
/src/DataManager/DataManager.cpp
1
#include "DataManager.hpp"
2

3
#include "Media/MediaDataFactory.hpp"
4
#include "ConcreteDataFactory.hpp"
5
#include "IO/LoaderRegistry.hpp"
6
#include "IO/LoaderRegistration.hpp"
7
#include "AnalogTimeSeries/Analog_Time_Series.hpp"
8
#include "DigitalTimeSeries/Digital_Event_Series.hpp"
9
#include "DigitalTimeSeries/Digital_Interval_Series.hpp"
10
#include "Lines/Line_Data.hpp"
11
#include "Masks/Mask_Data.hpp"
12
#include "Points/Point_Data.hpp"
13
#include "Tensors/Tensor_Data.hpp"
14

15
// Media includes - now from separate MediaData library
16
//#include "Media/Image_Data.hpp"
17
#include "Media/Media_Data.hpp"
18
//#include "Media/Video_Data.hpp"
19

20
#include "AnalogTimeSeries/IO/JSON/Analog_Time_Series_JSON.hpp"
21
#include "DigitalTimeSeries/IO/CSV/Digital_Interval_Series_CSV.hpp"
22
#include "DigitalTimeSeries/IO/JSON/Digital_Event_Series_JSON.hpp"
23
#include "DigitalTimeSeries/IO/JSON/Digital_Interval_Series_JSON.hpp"
24
#include "Lines/IO/JSON/Line_Data_JSON.hpp"
25
#include "Masks/IO/JSON/Mask_Data_JSON.hpp"
26
#ifdef ENABLE_OPENCV
27
#include "Media/IO/JSON/Image_Data_JSON.hpp"
28
#endif
29
#include "Points/IO/JSON/Point_Data_JSON.hpp"
30
#include "Tensors/IO/numpy/Tensor_Data_numpy.hpp"
31
#include "utils/TableView/TableRegistry.hpp"
32

33
#include "loaders/binary_loaders.hpp"
34
#include "transforms/Masks/mask_area.hpp"
35

36
#include "TimeFrame/TimeFrame.hpp"
37

38
#include "nlohmann/json.hpp"
39

40
#include "transforms/TransformPipeline.hpp"
41
#include "transforms/TransformRegistry.hpp"
42
#include "utils/string_manip.hpp"
43

44
#include "Entity/EntityRegistry.hpp"
45

46
#include <filesystem>
47
#include <fstream>
48
#include <iostream>
49
#include <optional>
50
#include <regex>
51

52
using namespace nlohmann;
53

54
/**
55
 * @brief Try loading data using new registry system first, fallback to legacy if needed
56
 */
57
bool tryRegistryThenLegacyLoad(
4✔
58
    DataManager* dm,
59
    std::string const& file_path,
60
    DM_DataType data_type,
61
    nlohmann::json const& item,
62
    std::string const& name,
63
    std::vector<DataInfo>& data_info_list,
64
    DataFactory* factory
65
) {
66
    // Extract format if available
67
    if (item.contains("format")) {
4✔
68
        std::string const format = item["format"];
4✔
69
        
70
        // Try registry system first
71
        LoaderRegistry& registry = LoaderRegistry::getInstance();
4✔
72
        if (registry.isFormatSupported(format, toIODataType(data_type))) {
4✔
73
            std::cout << "Using registry loader for " << name << " (format: " << format << ")" << std::endl;
4✔
74

75
            LoadResult result = registry.tryLoad(format, toIODataType(data_type), file_path, item, factory);
4✔
76
            if (result.success) {
4✔
77
                // Handle data setting and post-loading setup based on data type
78
                switch (data_type) {
3✔
79
                    case DM_DataType::Line: {
2✔
80
                        // Set the LineData in DataManager
81
                        if (std::holds_alternative<std::shared_ptr<LineData>>(result.data)) {
2✔
82
                            auto line_data = std::get<std::shared_ptr<LineData>>(result.data);
2✔
83
                            
84
                            // Set up identity context
85
                            if (line_data) {
2✔
86
                                line_data->setIdentityContext(name, dm->getEntityRegistry());
2✔
87
                                line_data->rebuildAllEntityIds();
2✔
88
                            }
89
                            
90
                            dm->setData<LineData>(name, line_data, TimeKey("time"));
2✔
91
                            
92
                            std::string const color = item.value("color", "0000FF");
2✔
93
                            data_info_list.push_back({name, "LineData", color});
6✔
94
                        }
2✔
95
                        break;
2✔
96
                    }
97
                    case DM_DataType::Mask: {
1✔
98
                        // Set the MaskData in DataManager
99
                        if (std::holds_alternative<std::shared_ptr<MaskData>>(result.data)) {
1✔
100
                            auto mask_data = std::get<std::shared_ptr<MaskData>>(result.data);
1✔
101
                            
102
                            dm->setData<MaskData>(name, mask_data, TimeKey("time"));
1✔
103
                            
104
                            std::string const color = item.value("color", "0000FF");
1✔
105
                            data_info_list.push_back({name, "MaskData", color});
3✔
106
                            
107
                            // Handle operations if present (same as legacy)
108
                            if (item.contains("operations")) {
1✔
109
                                for (auto const & operation: item["operations"]) {
×
110
                                    std::string const operation_type = operation["type"];
×
111
                                    if (operation_type == "area") {
×
112
                                        std::cout << "Calculating area for mask: " << name << std::endl;
×
113
                                        auto area_data = area(dm->getData<MaskData>(name).get());
×
114
                                        std::string const output_name = name + "_area";
×
UNCOV
115
                                        dm->setData<AnalogTimeSeries>(output_name, area_data, TimeKey("time"));
×
UNCOV
116
                                    }
×
UNCOV
117
                                }
×
118
                            }
119
                        }
1✔
120
                        break;
1✔
121
                    }
122
                    // Add other data types as they get plugin support...
UNCOV
123
                    default:
×
UNCOV
124
                        std::cerr << "Registry loaded unsupported data type: " << static_cast<int>(data_type) << std::endl;
×
UNCOV
125
                        return false;
×
126
                }
127
                
128
                return true;
3✔
129
            } else {
130
                std::cout << "Registry loading failed for " << name << ": " << result.error_message 
131
                         << ", falling back to legacy loader" << std::endl;
1✔
132
            }
133
        }
4✔
134
    }
4✔
135
    
136
    return false; // Indicates we should use legacy loading
1✔
137
}
9✔
138

139
DataManager::DataManager() {
191✔
140
    _times[TimeKey("time")] = std::make_shared<TimeFrame>();
191✔
141
    _data["media"] = std::make_shared<EmptyMediaData>();
573✔
142

143
    setTimeKey("media", TimeKey("time"));
573✔
144
    _output_path = std::filesystem::current_path();
191✔
145

146
    // Initialize TableRegistry
147
    _table_registry = std::make_unique<TableRegistry>(*this);
191✔
148

149
    // Initialize EntityRegistry
150
    _entity_registry = std::make_unique<EntityRegistry>();
191✔
151

152
    // Register all available loaders
153
    static bool loaders_registered = false;
154
    if (!loaders_registered) {
191✔
155
        registerAllLoaders();
90✔
156
        loaders_registered = true;
90✔
157
    }
158
}
191✔
159

160
DataManager::~DataManager() = default;
191✔
161

UNCOV
162
void DataManager::reset() {
×
163
    std::cout << "DataManager: Resetting to initial state..." << std::endl;
×
164
    
165
    // Clear all data objects except media (which we'll reset)
166
    _data.clear();
×
167
    
168
    // Reset media to a fresh empty MediaData object
UNCOV
169
    _data["media"] = std::make_shared<EmptyMediaData>();
×
170
    
171
    // Clear all TimeFrame objects except the default "time" frame
UNCOV
172
    _times.clear();
×
173
    
174
    // Recreate the default "time" TimeFrame
UNCOV
175
    _times[TimeKey("time")] = std::make_shared<TimeFrame>();
×
176
    
177
    // Clear all data-to-timeframe mappings and recreate the default media mapping
178
    _time_frames.clear();
×
UNCOV
179
    setTimeKey("media", TimeKey("time"));
×
180

181
    
182
    // Reset current time
183
    _current_time = 0;
×
184
    
185
    // Notify observers that the state has changed
186
    _notifyObservers();
×
187
    
UNCOV
188
    std::cout << "DataManager: Reset complete. Default 'time' frame and 'media' data restored." << std::endl;
×
189

190
    // Reset entity registry for a new session context
UNCOV
191
    if (_entity_registry) {
×
UNCOV
192
         _entity_registry->clear();
×
193
    }
UNCOV
194
}
×
195

196
bool DataManager::setTime(TimeKey const & key, std::shared_ptr<TimeFrame> timeframe, bool overwrite) {
201✔
197

198
    if (!timeframe) {
201✔
199
        std::cerr << "Error: Cannot register a nullptr TimeFrame for key: " << key << std::endl;
1✔
200
        return false;
1✔
201
    }
202

203
    if (_times.find(key) != _times.end()) {
200✔
204
        if (!overwrite) {
6✔
205
            std::cerr << "Error: Time key already exists in DataManager: " << key << std::endl;
2✔
206
            return false;
2✔
207
        }
208
    }
209

210
    _times[key] = std::move(timeframe);
198✔
211

212
    // Move ptr to new time frame to all data that hold 
213
    for (auto const & [data_key, data] : _data) {
397✔
214
        if (_time_frames.find(data_key) != _time_frames.end()) {
199✔
215
            auto time_key = _time_frames[data_key];
199✔
216
            if (time_key == key) {
199✔
217
                std::visit([this, timeframe](auto & x) {
8✔
218
                    x->setTimeFrame(timeframe);
4✔
219
                },
4✔
220
                           data);
221
            }
222
        }
199✔
223
    }
224

225
    return true;
198✔
226
}
227

228
std::shared_ptr<TimeFrame> DataManager::getTime() {
8✔
229
    return _times[TimeKey("time")];
8✔
230
};
231

232
std::shared_ptr<TimeFrame> DataManager::getTime(TimeKey const & key) {
179✔
233
    if (_times.find(key) != _times.end()) {
179✔
234
        return _times[key];
177✔
235
    }
236
    return nullptr;
2✔
237
};
238

239
TimeIndexAndFrame DataManager::getCurrentIndexAndFrame(TimeKey const & key) {
×
UNCOV
240
    if (_times.find(key) != _times.end()) {
×
241
        return {TimeFrameIndex(_current_time), _times[key]};
×
242
    }
243
    return {TimeFrameIndex(_current_time), nullptr};
×
244
}
×
245

UNCOV
246
bool DataManager::removeTime(TimeKey const & key) {
×
247
    if (_times.find(key) == _times.end()) {
×
248
        std::cerr << "Error: could not find time key in DataManager: " << key << std::endl;
×
249
        return false;
×
250
    }
251

UNCOV
252
    auto it = _times.find(key);
×
UNCOV
253
    _times.erase(it);
×
UNCOV
254
    return true;
×
255
}
256

257
bool DataManager::setTimeKey(std::string const & data_key, TimeKey const & time_key) {
629✔
258
    if (_data.find(data_key) == _data.end()) {
629✔
259
        std::cerr << "Error: Data key not found in DataManager: " << data_key << std::endl;
3✔
260
        return false;
3✔
261
    }
262

263
    if (_times.find(time_key) == _times.end()) {
626✔
264
        std::cerr << "Error: Time key not found in DataManager: " << time_key << std::endl;
1✔
265
        return false;
1✔
266
    }
267

268
    _time_frames[data_key] = time_key;
625✔
269

270
    if (_data.find(data_key) != _data.end()) {
625✔
271
        auto data = _data[data_key];
625✔
272
        std::visit([this, time_key](auto & x) {
1,250✔
273
            x->setTimeFrame(this->_times[time_key]);
625✔
274
        },
625✔
275
                   data);
276
    }
625✔
277
    return true;
625✔
278
}
279

280
TimeKey DataManager::getTimeKey(std::string const & data_key) {
131✔
281
    // check if data_key exists
282
    if (_data.find(data_key) == _data.end()) {
131✔
283
        std::cerr << "Error: Data key not found in DataManager: " << data_key << std::endl;
1✔
284
        return TimeKey("");
1✔
285
    }
286

287
    // check if data key has time frame
288
    if (_time_frames.find(data_key) == _time_frames.end()) {
130✔
289
        std::cerr << "Error: Data key "
290
                  << data_key
UNCOV
291
                  << " exists, but not assigned to a TimeFrame" << std::endl;
×
UNCOV
292
        return TimeKey("");
×
293
    }
294

295
    return _time_frames[data_key];
130✔
296
}
297

298
std::vector<TimeKey> DataManager::getTimeFrameKeys() {
8✔
299
    std::vector<TimeKey> keys;
8✔
300
    keys.reserve(_times.size());
8✔
301
    for (auto const & [key, value]: _times) {
24✔
302

303
        keys.push_back(key);
16✔
304
    }
305
    return keys;
8✔
UNCOV
306
}
×
307

308
int DataManager::addCallbackToData(std::string const & key, ObserverCallback callback) {
7✔
309

310
    int id = -1;
7✔
311

312
    if (_data.find(key) != _data.end()) {
7✔
313
        auto data = _data[key];
6✔
314

315
        id = std::visit([callback](auto & x) {
12✔
316
            return x.get()->addObserver(callback);
6✔
317
        },
318
                        data);
319
    }
6✔
320

321
    return id;
7✔
322
}
323

324
bool DataManager::removeCallbackFromData(std::string const & key, int callback_id) {
4✔
325
    if (_data.find(key) != _data.end()) {
4✔
326
        auto data = _data[key];
3✔
327

328
        std::visit([callback_id](auto & x) {
6✔
329
            x.get()->removeObserver(callback_id);
3✔
330
        },
3✔
331
                   data);
332

333
        return true;
3✔
334
    }
3✔
335

336
    return false;
1✔
337
}
338

339
void DataManager::addObserver(ObserverCallback callback) {
6✔
340
    _observers.push_back(std::move(callback));
6✔
341
}
6✔
342

343
void DataManager::_notifyObservers() {
432✔
344
    for (auto & observer: _observers) {
440✔
345
        observer();
8✔
346
    }
347
}
432✔
348

349
// ===== Table Registry accessors =====
350
TableRegistry * DataManager::getTableRegistry() {
47✔
351
    return _table_registry.get();
47✔
352
}
353

354
TableRegistry const * DataManager::getTableRegistry() const {
×
355
    return _table_registry.get();
×
356
}
357

358
// ===== Table observer channel =====
UNCOV
359
int DataManager::addTableObserver(TableObserver callback) {
×
UNCOV
360
    if (!callback) return -1;
×
361
    int id = _next_table_observer_id++;
×
362
    _table_observers[id] = std::move(callback);
×
UNCOV
363
    return id;
×
364
}
365

UNCOV
366
bool DataManager::removeTableObserver(int callback_id) {
×
UNCOV
367
    return _table_observers.erase(callback_id) > 0;
×
368
}
369

370
void DataManager::notifyTableObservers(TableEvent const & ev) {
43✔
371
    for (auto const & [id, cb] : _table_observers) {
43✔
372
        (void)id;
UNCOV
373
        cb(ev);
×
374
    }
375
}
43✔
376

377
// Provide C-style bridge for TableRegistry to call
378
void DataManager__NotifyTableObservers(DataManager & dm, TableEvent const & ev) {
43✔
379
    dm.notifyTableObservers(ev);
43✔
380
}
43✔
381

382
std::vector<std::string> DataManager::getAllKeys() {
5✔
383
    std::vector<std::string> keys;
5✔
384
    keys.reserve(_data.size());
5✔
385
    for (auto const & [key, value]: _data) {
17✔
386

387
        keys.push_back(key);
12✔
388
    }
389
    return keys;
5✔
UNCOV
390
}
×
391

UNCOV
392
std::optional<DataTypeVariant> DataManager::getDataVariant(std::string const & key) {
×
UNCOV
393
    if (_data.find(key) != _data.end()) {
×
UNCOV
394
        return _data[key];
×
395
    }
UNCOV
396
    return std::nullopt;
×
397
}
398

399
void DataManager::setData(std::string const & key, DataTypeVariant data, TimeKey const & time_key) {
1✔
400
    _data[key] = data;
1✔
401
    setTimeKey(key, time_key);
1✔
402
    _notifyObservers();
1✔
403
}
1✔
404

405
std::optional<std::string> processFilePath(
12✔
406
        std::string const & file_path,
407
        std::filesystem::path const & base_path) {
408
    std::filesystem::path full_path = file_path;
12✔
409

410
    // Check for wildcard character
411
    if (file_path.find('*') != std::string::npos) {
12✔
412
        // Convert wildcard pattern to regex
413
        std::string const pattern = std::regex_replace(full_path.string(), std::regex("\\*"), ".*");
×
414
        std::regex const regex_pattern(pattern);
×
415

416
        // Iterate through the directory to find matching files
UNCOV
417
        for (auto const & entry: std::filesystem::directory_iterator(base_path)) {
×
418
            std::cout << "Checking " << entry.path().string() << " with full path " << full_path << std::endl;
×
419
            if (std::regex_match(entry.path().string(), regex_pattern)) {
×
420
                std::cout << "Loading file " << entry.path().string() << std::endl;
×
UNCOV
421
                return entry.path().string();
×
422
            }
423
        }
×
UNCOV
424
        return std::nullopt;
×
UNCOV
425
    } else {
×
426
        // Check if the file path is relative
427
        if (!std::filesystem::path(file_path).is_absolute()) {
12✔
UNCOV
428
            full_path = base_path / file_path;
×
429
        }
430
        // Check for the presence of the file
431
        if (std::filesystem::exists(full_path)) {
12✔
432
            std::cout << "Loading file " << full_path.string() << std::endl;
6✔
433
            return full_path.string();
6✔
434
        } else {
435
            return std::nullopt;
6✔
436
        }
437
    }
438
}
12✔
439

440
bool checkRequiredFields(json const & item, std::vector<std::string> const & requiredFields) {
12✔
441
    for (auto const & field: requiredFields) {
48✔
442
        if (!item.contains(field)) {
36✔
UNCOV
443
            std::cerr << "Error: Missing required field \"" << field << "\" in JSON item." << std::endl;
×
UNCOV
444
            return false;
×
445
        }
446
    }
447
    return true;
12✔
448
}
449

UNCOV
450
void checkOptionalFields(json const & item, std::vector<std::string> const & optionalFields) {
×
451
    for (auto const & field: optionalFields) {
×
UNCOV
452
        if (!item.contains(field)) {
×
UNCOV
453
            std::cout << "Warning: Optional field \"" << field << "\" is missing in JSON item." << std::endl;
×
454
        }
455
    }
UNCOV
456
}
×
457

458
DM_DataType stringToDataType(std::string const & data_type_str) {
12✔
459
    if (data_type_str == "video") return DM_DataType::Video;
12✔
460
    if (data_type_str == "images") return DM_DataType::Images;
12✔
461
    if (data_type_str == "points") return DM_DataType::Points;
12✔
462
    if (data_type_str == "mask") return DM_DataType::Mask;
12✔
463
    if (data_type_str == "line") return DM_DataType::Line;
10✔
464
    if (data_type_str == "analog") return DM_DataType::Analog;
4✔
465
    if (data_type_str == "digital_event") return DM_DataType::DigitalEvent;
2✔
466
    if (data_type_str == "digital_interval") return DM_DataType::DigitalInterval;
2✔
UNCOV
467
    if (data_type_str == "tensor") return DM_DataType::Tensor;
×
UNCOV
468
    if (data_type_str == "time") return DM_DataType::Time;
×
UNCOV
469
    return DM_DataType::Unknown;
×
470
}
471

472
std::vector<DataInfo> load_data_from_json_config(DataManager * dm, std::string const & json_filepath) {
12✔
473
    std::vector<DataInfo> data_info_list;
12✔
474
    // Open JSON file
475
    std::ifstream ifs(json_filepath);
12✔
476
    if (!ifs.is_open()) {
12✔
UNCOV
477
        std::cerr << "Failed to open JSON file: " << json_filepath << std::endl;
×
UNCOV
478
        return data_info_list;
×
479
    }
480

481
    // Parse JSON
482
    json j;
12✔
483
    ifs >> j;
12✔
484

485
    // get base path of filepath
486
    std::filesystem::path const base_path = std::filesystem::path(json_filepath).parent_path();
12✔
487

488
    // Create factory for plugin system
489
    ConcreteDataFactory factory;
12✔
490

491
    // Iterate through JSON array
492
    for (auto const & item: j) {
24✔
493

494
        // Skip transformation objects - they will be processed separately
495
        if (item.contains("transformations")) {
12✔
UNCOV
496
            continue;
×
497
        }
498

499
        if (!checkRequiredFields(item, {"data_type", "name", "filepath"})) {
36✔
UNCOV
500
            continue;// Exit if any required field is missing
×
501
        }
502

503
        std::string const data_type_str = item["data_type"];
12✔
504
        auto const data_type = stringToDataType(data_type_str);
12✔
505
        if (data_type == DM_DataType::Unknown) {
12✔
UNCOV
506
            std::cout << "Unknown data type: " << data_type_str << std::endl;
×
UNCOV
507
            continue;
×
508
        }
509

510
        std::string const name = item["name"];
12✔
511

512
        auto file_exists = processFilePath(item["filepath"], base_path);
12✔
513
        if (!file_exists) {
12✔
514
            std::cerr << "File does not exist: " << item["filepath"] << std::endl;
6✔
515
            continue;
6✔
516
        }
517

518
        std::string const file_path = file_exists.value();
6✔
519

520
        switch (data_type) {
6✔
UNCOV
521
            case DM_DataType::Video: {
×
522
                auto media_data = MediaDataFactory::loadMediaData(data_type, file_path, item);
×
UNCOV
523
                if (media_data) {
×
524
                    dm->setData<MediaData>("media", media_data, TimeKey("time"));
×
525
                    data_info_list.push_back({name, "VideoData", ""});
×
526
                } else {
527
                    std::cerr << "Failed to load video data: " << file_path << std::endl;
×
528
                }
529
                break;
×
530
            }
×
531
#ifdef ENABLE_OPENCV
UNCOV
532
            case DM_DataType::Images: {
×
533
                auto media_data = MediaDataFactory::loadMediaData(data_type, file_path, item);
×
UNCOV
534
                if (media_data) {
×
535
                    dm->setData<MediaData>("media", media_data, TimeKey("time"));
×
536
                    data_info_list.push_back({name, "ImageData", ""});
×
537
                } else {
538
                    std::cerr << "Failed to load image data: " << file_path << std::endl;
×
539
                }
540
                break;
×
UNCOV
541
            }
×
542
#endif
543
            case DM_DataType::Points: {
×
544

545
                auto point_data = load_into_PointData(file_path, item);
×
546

547
                // Attach identity context and generate EntityIds
548
                if (point_data) {
×
UNCOV
549
                    point_data->setIdentityContext(name, dm->getEntityRegistry());
×
550
                    point_data->rebuildAllEntityIds();
×
551
                }
552

553
                dm->setData<PointData>(name, point_data, TimeKey("time"));
×
554

UNCOV
555
                std::string const color = item.value("color", "#0000FF");
×
UNCOV
556
                data_info_list.push_back({name, "PointData", color});
×
UNCOV
557
                break;
×
UNCOV
558
            }
×
559
            case DM_DataType::Mask: {
1✔
560

561
                // Try registry system first, then fallback to legacy
562
                if (tryRegistryThenLegacyLoad(dm, file_path, data_type, item, name, data_info_list, &factory)) {
1✔
563
                    break; // Successfully loaded with plugin
1✔
564
                }
565

566
                // Legacy loading fallback
567
                auto mask_data = load_into_MaskData(file_path, item);
×
568

569
                std::string const color = item.value("color", "0000FF");
×
UNCOV
570
                dm->setData<MaskData>(name, mask_data, TimeKey("time"));
×
571

UNCOV
572
                data_info_list.push_back({name, "MaskData", color});
×
573

UNCOV
574
                if (item.contains("operations")) {
×
575

576
                    for (auto const & operation: item["operations"]) {
×
577

578
                        std::string const operation_type = operation["type"];
×
579

580
                        if (operation_type == "area") {
×
581
                            std::cout << "Calculating area for mask: " << name << std::endl;
×
UNCOV
582
                            auto area_data = area(dm->getData<MaskData>(name).get());
×
583
                            std::string const output_name = name + "_area";
×
584
                            dm->setData<AnalogTimeSeries>(output_name, area_data, TimeKey("time"));
×
UNCOV
585
                        }
×
UNCOV
586
                    }
×
587
                }
UNCOV
588
                break;
×
UNCOV
589
            }
×
590
            case DM_DataType::Line: {
3✔
591
                
592
                // Try registry system first, then fallback to legacy
593
                if (tryRegistryThenLegacyLoad(dm, file_path, data_type, item, name, data_info_list, &factory)) {
3✔
594
                    break; // Successfully loaded with plugin
2✔
595
                }
596
                
597
                // Legacy loading fallback
598
                auto line_data = load_into_LineData(file_path, item);
1✔
599

600
                // Attach identity context and generate EntityIds
601
                if (line_data) {
1✔
602
                    line_data->setIdentityContext(name, dm->getEntityRegistry());
1✔
603
                    line_data->rebuildAllEntityIds();
1✔
604
                }
605

606
                dm->setData<LineData>(name, line_data, TimeKey("time"));
1✔
607

608
                std::string const color = item.value("color", "0000FF");
1✔
609

610
                data_info_list.push_back({name, "LineData", color});
3✔
611

612
                break;
1✔
613
            }
1✔
614
            case DM_DataType::Analog: {
1✔
615

616
                auto analog_time_series = load_into_AnalogTimeSeries(file_path, item);
1✔
617

618
                for (size_t channel = 0; channel < analog_time_series.size(); channel++) {
2✔
619
                    std::string const channel_name = name + "_" + std::to_string(channel);
1✔
620

621
                    dm->setData<AnalogTimeSeries>(channel_name, analog_time_series[channel], TimeKey("time"));
1✔
622

623
                    if (item.contains("clock")) {
1✔
UNCOV
624
                        std::string const clock_str = item["clock"];
×
UNCOV
625
                        auto const clock = TimeKey(clock_str);
×
626
                        dm->setTimeKey(channel_name, clock);
×
UNCOV
627
                    }
×
628
                }
1✔
629
                break;
1✔
630
            }
1✔
631
            case DM_DataType::DigitalEvent: {
×
632

UNCOV
633
                auto digital_event_series = load_into_DigitalEventSeries(file_path, item);
×
634

NEW
635
                for (size_t channel = 0; channel < digital_event_series.size(); channel++) {
×
636
                    std::string const channel_name = name + "_" + std::to_string(channel);
×
637

638
                    // Attach identity context and generate EntityIds
639
                    if (digital_event_series[channel]) {
×
UNCOV
640
                        digital_event_series[channel]->setIdentityContext(channel_name, dm->getEntityRegistry());
×
641
                        digital_event_series[channel]->rebuildAllEntityIds();
×
642
                    }
643

644
                    dm->setData<DigitalEventSeries>(channel_name, digital_event_series[channel], TimeKey("time"));
×
645

646
                    if (item.contains("clock")) {
×
647
                        std::string const clock_str = item["clock"];
×
648
                        auto const clock = TimeKey(clock_str);
×
UNCOV
649
                        dm->setTimeKey(channel_name, clock);
×
UNCOV
650
                    }
×
UNCOV
651
                }
×
UNCOV
652
                break;
×
UNCOV
653
            }
×
654
            case DM_DataType::DigitalInterval: {
1✔
655

656
                auto digital_interval_series = load_into_DigitalIntervalSeries(file_path, item);
1✔
657
                if (digital_interval_series) {
1✔
658
                    digital_interval_series->setIdentityContext(name, dm->getEntityRegistry());
1✔
659
                    digital_interval_series->rebuildAllEntityIds();
1✔
660
                }
661
                dm->setData<DigitalIntervalSeries>(name, digital_interval_series, TimeKey("time"));
1✔
662

663
                break;
1✔
664
            }
1✔
665
            case DM_DataType::Tensor: {
×
666

667
                if (item["format"] == "numpy") {
×
668

669
                    TensorData tensor_data;
×
670
                    loadNpyToTensorData(file_path, tensor_data);
×
671

672
                    dm->setData<TensorData>(name, std::make_shared<TensorData>(tensor_data), TimeKey("time"));
×
673

674
                } else {
×
UNCOV
675
                    std::cout << "Format " << item["format"] << " not found for " << name << std::endl;
×
676
                }
UNCOV
677
                break;
×
678
            }
679
            case DM_DataType::Time: {
×
680

681
                if (item["format"] == "uint16") {
×
682

683
                    int const channel = item["channel"];
×
684
                    std::string const transition = item["transition"];
×
685

UNCOV
686
                    int const header_size = item.value("header_size", 0);
×
687

688
                    auto opts = Loader::BinaryAnalogOptions{.file_path = file_path,
×
UNCOV
689
                                                            .header_size_bytes = static_cast<size_t>(header_size)};
×
UNCOV
690
                    auto data = Loader::readBinaryFile<uint16_t>(opts);
×
691

692
                    auto digital_data = Loader::extractDigitalData(data, channel);
×
693
                    auto events = Loader::extractEvents(digital_data, transition);
×
694

695
                    // convert to int with std::transform
696
                    std::vector<int> events_int;
×
UNCOV
697
                    events_int.reserve(events.size());
×
698
                    for (auto e: events) {
×
699
                        events_int.push_back(static_cast<int>(e));
×
700
                    }
UNCOV
701
                    std::cout << "Loaded " << events_int.size() << " events for " << name << std::endl;
×
702

UNCOV
703
                    auto timeframe = std::make_shared<TimeFrame>(events_int);
×
704
                    dm->setTime(TimeKey(name), timeframe, true);
×
UNCOV
705
                }
×
706

707
                if (item["format"] == "uint16_length") {
×
708

UNCOV
709
                    int const header_size = item.value("header_size", 0);
×
710

711
                    auto opts = Loader::BinaryAnalogOptions{.file_path = file_path,
×
UNCOV
712
                                                            .header_size_bytes = static_cast<size_t>(header_size)};
×
713
                    auto data = Loader::readBinaryFile<uint16_t>(opts);
×
714

715
                    std::vector<int> t(data.size());
×
716
                    std::iota(std::begin(t), std::end(t), 0);
×
717

UNCOV
718
                    std::cout << "Total of " << t.size() << " timestamps for " << name << std::endl;
×
719

UNCOV
720
                    auto timeframe = std::make_shared<TimeFrame>(t);
×
UNCOV
721
                    dm->setTime(TimeKey(name), timeframe, true);
×
722
                }
×
723

UNCOV
724
                if (item["format"] == "filename") {
×
725

726
                    // Get required parameters
727
                    std::string const folder_path = file_path;// file path is required argument
×
728
                    std::string const regex_pattern = item["regex_pattern"];
×
729

730
                    // Get optional parameters with defaults
731
                    std::string const file_extension = item.value("file_extension", "");
×
732
                    std::string const mode_str = item.value("mode", "found_values");
×
733
                    bool const sort_ascending = item.value("sort_ascending", true);
×
734

735
                    // Convert mode string to enum
UNCOV
736
                    FilenameTimeFrameMode mode = FilenameTimeFrameMode::FOUND_VALUES;
×
UNCOV
737
                    if (mode_str == "zero_to_max") {
×
UNCOV
738
                        mode = FilenameTimeFrameMode::ZERO_TO_MAX;
×
739
                    } else if (mode_str == "min_to_max") {
×
740
                        mode = FilenameTimeFrameMode::MIN_TO_MAX;
×
741
                    }
742

743
                    // Create options
744
                    FilenameTimeFrameOptions options;
×
UNCOV
745
                    options.folder_path = folder_path;
×
UNCOV
746
                    options.file_extension = file_extension;
×
747
                    options.regex_pattern = regex_pattern;
×
748
                    options.mode = mode;
×
749
                    options.sort_ascending = sort_ascending;
×
750

751
                    // Create TimeFrame from filenames
UNCOV
752
                    auto timeframe = createTimeFrameFromFilenames(options);
×
UNCOV
753
                    if (timeframe) {
×
754
                        dm->setTime(TimeKey(name), timeframe, true);
×
755
                        std::cout << "Created TimeFrame '" << name << "' from filenames in "
756
                                  << folder_path << std::endl;
×
757
                    } else {
758
                        std::cerr << "Error: Failed to create TimeFrame from filenames for "
759
                                  << name << std::endl;
×
760
                    }
761
                }
×
762
                break;
×
763
            }
764
            default:
×
765
                std::cout << "Unsupported data type: " << data_type_str << std::endl;
×
766
                continue;
×
767
        }
×
768
        if (item.contains("clock")) {
6✔
UNCOV
769
            std::string clock_str = item["clock"];
×
UNCOV
770
            auto clock = TimeKey(clock_str);
×
UNCOV
771
            std::cout << "Setting time for " << name << " to " << clock << std::endl;
×
UNCOV
772
            dm->setTimeKey(name, clock);
×
UNCOV
773
        }
×
774
    }
24✔
775

776
    // Process all transformation objects found in the JSON array
777
    for (auto const & item: j) {
24✔
778
        if (item.contains("transformations")) {
12✔
779
            std::cout << "Found transformations section, executing pipeline..." << std::endl;
×
780
            
781
            try {
782
                // Create registry and pipeline with proper constructors
783
                auto registry = std::make_unique<TransformRegistry>();
×
784
                TransformPipeline pipeline(dm, registry.get());
×
785
                
786
                // Load the pipeline configuration from JSON
UNCOV
787
                if (!pipeline.loadFromJson(item["transformations"])) {
×
788
                    std::cerr << "Failed to load pipeline configuration from JSON" << std::endl;
×
789
                    continue;
×
790
                }
791
                
792
                // Execute the pipeline with a progress callback
793
                auto result = pipeline.execute([](int step_index, const std::string& step_name, int step_progress, int overall_progress) {
×
794
                    std::cout << "Step " << step_index << " ('" << step_name << "'): " 
×
795
                              << step_progress << "% (Overall: " << overall_progress << "%)" << std::endl;
×
796
                });
×
797
                
798
                if (result.success) {
×
UNCOV
799
                    std::cout << "Pipeline executed successfully!" << std::endl;
×
UNCOV
800
                    std::cout << "Steps completed: " << result.steps_completed << "/" << result.total_steps << std::endl;
×
801
                    std::cout << "Total execution time: " << result.total_execution_time_ms << " ms" << std::endl;
×
802
                } else {
803
                    std::cerr << "Pipeline execution failed: " << result.error_message << std::endl;
×
804
                }
805
                
UNCOV
806
            } catch (const std::exception& e) {
×
UNCOV
807
                std::cerr << "Exception during pipeline execution: " << e.what() << std::endl;
×
UNCOV
808
            }
×
809
        }
810
    }
811

812
    return data_info_list;
813
}
15✔
814

815
DM_DataType DataManager::getType(std::string const & key) const {
25✔
816
    auto it = _data.find(key);
25✔
817
    if (it != _data.end()) {
25✔
818
        if (std::holds_alternative<std::shared_ptr<MediaData>>(it->second)) {
22✔
819
            auto media_data = std::get<std::shared_ptr<MediaData>>(it->second);
18✔
820
            switch (media_data->getMediaType()) {
18✔
821
                case MediaData::MediaType::Video:
11✔
822
                    return DM_DataType::Video;
11✔
823
                case MediaData::MediaType::Images:
7✔
824
                    return DM_DataType::Images;
7✔
825
                case MediaData::MediaType::HDF5:
×
826
                    // For HDF5, we might need additional logic to determine if it's video or images
827
                    // For now, defaulting to Video (old behavior)
UNCOV
828
                    return DM_DataType::Video;
×
UNCOV
829
                default:
×
UNCOV
830
                    return DM_DataType::Video; // Old behavior for unknown types
×
831
            }
832
        } else if (std::holds_alternative<std::shared_ptr<PointData>>(it->second)) {
22✔
833
            return DM_DataType::Points;
2✔
834
        } else if (std::holds_alternative<std::shared_ptr<LineData>>(it->second)) {
2✔
835
            return DM_DataType::Line;
1✔
836
        } else if (std::holds_alternative<std::shared_ptr<MaskData>>(it->second)) {
1✔
837
            return DM_DataType::Mask;
1✔
838
        } else if (std::holds_alternative<std::shared_ptr<AnalogTimeSeries>>(it->second)) {
×
839
            return DM_DataType::Analog;
×
840
        } else if (std::holds_alternative<std::shared_ptr<DigitalEventSeries>>(it->second)) {
×
UNCOV
841
            return DM_DataType::DigitalEvent;
×
842
        } else if (std::holds_alternative<std::shared_ptr<DigitalIntervalSeries>>(it->second)) {
×
UNCOV
843
            return DM_DataType::DigitalInterval;
×
UNCOV
844
        } else if (std::holds_alternative<std::shared_ptr<TensorData>>(it->second)) {
×
UNCOV
845
            return DM_DataType::Tensor;
×
846
        }
847
        return DM_DataType::Unknown;
×
848
    }
849
    return DM_DataType::Unknown;
3✔
850
}
851

852
std::string convert_data_type_to_string(DM_DataType type) {
×
853
    switch (type) {
×
854
        case DM_DataType::Video:
×
855
            return "video";
×
856
        case DM_DataType::Images:
×
857
            return "images";
×
858
        case DM_DataType::Points:
×
859
            return "points";
×
860
        case DM_DataType::Mask:
×
861
            return "mask";
×
862
        case DM_DataType::Line:
×
863
            return "line";
×
864
        case DM_DataType::Analog:
×
865
            return "analog";
×
866
        case DM_DataType::DigitalEvent:
×
867
            return "digital_event";
×
868
        case DM_DataType::DigitalInterval:
×
869
            return "digital_interval";
×
870
        case DM_DataType::Tensor:
×
UNCOV
871
            return "tensor";
×
UNCOV
872
        case DM_DataType::Time:
×
UNCOV
873
            return "time";
×
UNCOV
874
        default:
×
UNCOV
875
            return "unknown";
×
876
    }
877
}
878

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