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

paulmthompson / WhiskerToolbox / 17446119178

03 Sep 2025 09:06PM UTC coverage: 70.31% (-1.9%) from 72.184%
17446119178

push

github

paulmthompson
fix life cycle issues with data viewer widget. It now cleans up references it holds if something is deleted from data manager that it is plotting

243 of 273 new or added lines in 3 files covered. (89.01%)

130 existing lines in 3 files now uncovered.

33739 of 47986 relevant lines covered (70.31%)

1302.4 hits per line

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

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

3
#include "AnalogTimeSeries/Analog_Time_Series.hpp"
4
#include "ConcreteDataFactory.hpp"
5
#include "DigitalTimeSeries/Digital_Event_Series.hpp"
6
#include "DigitalTimeSeries/Digital_Interval_Series.hpp"
7
#include "IO/LoaderRegistration.hpp"
8
#include "IO/LoaderRegistry.hpp"
9
#include "Lines/Line_Data.hpp"
10
#include "Masks/Mask_Data.hpp"
11
#include "Media/MediaDataFactory.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

35
#include "TimeFrame/TimeFrame.hpp"
36

37
#include "nlohmann/json.hpp"
38

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

43
#include "Entity/EntityRegistry.hpp"
44

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

51
using namespace nlohmann;
52

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

68
        // Try registry system first
69
        LoaderRegistry & registry = LoaderRegistry::getInstance();
4✔
70
        if (registry.isFormatSupported(format, toIODataType(data_type))) {
4✔
71
            std::cout << "Using registry loader for " << name << " (format: " << format << ")" << std::endl;
4✔
72

73
            LoadResult result = registry.tryLoad(format, toIODataType(data_type), file_path, item, factory);
4✔
74
            if (result.success) {
4✔
75
                // Handle data setting and post-loading setup based on data type
76
                switch (data_type) {
3✔
77
                    case DM_DataType::Line: {
2✔
78
                        // Set the LineData in DataManager
79
                        if (std::holds_alternative<std::shared_ptr<LineData>>(result.data)) {
2✔
80
                            auto line_data = std::get<std::shared_ptr<LineData>>(result.data);
2✔
81

82
                            // Set up identity context
83
                            if (line_data) {
2✔
84
                                line_data->setIdentityContext(name, dm->getEntityRegistry());
2✔
85
                                line_data->rebuildAllEntityIds();
2✔
86
                            }
87

88
                            dm->setData<LineData>(name, line_data, TimeKey("time"));
2✔
89

90
                            std::string const color = item.value("color", "0000FF");
2✔
91
                            data_info_list.push_back({name, "LineData", color});
6✔
92
                        }
2✔
93
                        break;
2✔
94
                    }
95
                    case DM_DataType::Mask: {
1✔
96
                        // Set the MaskData in DataManager
97
                        if (std::holds_alternative<std::shared_ptr<MaskData>>(result.data)) {
1✔
98
                            auto mask_data = std::get<std::shared_ptr<MaskData>>(result.data);
1✔
99

100
                            dm->setData<MaskData>(name, mask_data, TimeKey("time"));
1✔
101

102
                            std::string const color = item.value("color", "0000FF");
1✔
103
                            data_info_list.push_back({name, "MaskData", color});
3✔
104

105
                        }
1✔
106
                        break;
1✔
107
                    }
108
                    // Add other data types as they get plugin support...
109
                    default:
×
110
                        std::cerr << "Registry loaded unsupported data type: " << static_cast<int>(data_type) << std::endl;
×
111
                        return false;
×
112
                }
113

114
                return true;
3✔
115
            } else {
116
                std::cout << "Registry loading failed for " << name << ": " << result.error_message
117
                          << ", falling back to legacy loader" << std::endl;
1✔
118
            }
119
        }
4✔
120
    }
4✔
121

122
    return false;// Indicates we should use legacy loading
1✔
123
}
9✔
124

125
DataManager::DataManager() {
251✔
126
    _times[TimeKey("time")] = std::make_shared<TimeFrame>();
251✔
127
    _data["media"] = std::make_shared<EmptyMediaData>();
753✔
128

129
    setTimeKey("media", TimeKey("time"));
753✔
130
    _output_path = std::filesystem::current_path();
251✔
131

132
    // Initialize TableRegistry
133
    _table_registry = std::make_unique<TableRegistry>(*this);
251✔
134

135
    // Initialize EntityRegistry
136
    _entity_registry = std::make_unique<EntityRegistry>();
251✔
137

138
    // Register all available loaders
139
    static bool loaders_registered = false;
140
    if (!loaders_registered) {
251✔
141
        registerAllLoaders();
138✔
142
        loaders_registered = true;
138✔
143
    }
144
}
251✔
145

146
DataManager::~DataManager() = default;
251✔
147

148
void DataManager::reset() {
×
149
    std::cout << "DataManager: Resetting to initial state..." << std::endl;
×
150

151
    // Clear all data objects except media (which we'll reset)
152
    _data.clear();
×
153

154
    // Reset media to a fresh empty MediaData object
155
    _data["media"] = std::make_shared<EmptyMediaData>();
×
156

157
    // Clear all TimeFrame objects except the default "time" frame
158
    _times.clear();
×
159

160
    // Recreate the default "time" TimeFrame
161
    _times[TimeKey("time")] = std::make_shared<TimeFrame>();
×
162

163
    // Clear all data-to-timeframe mappings and recreate the default media mapping
164
    _time_frames.clear();
×
165
    setTimeKey("media", TimeKey("time"));
×
166

167

168
    // Reset current time
169
    _current_time = 0;
×
170

171
    // Notify observers that the state has changed
172
    _notifyObservers();
×
173

174
    std::cout << "DataManager: Reset complete. Default 'time' frame and 'media' data restored." << std::endl;
×
175

176
    // Reset entity registry for a new session context
177
    if (_entity_registry) {
×
178
        _entity_registry->clear();
×
179
    }
180
}
×
181

182
bool DataManager::setTime(TimeKey const & key, std::shared_ptr<TimeFrame> timeframe, bool overwrite) {
261✔
183

184
    if (!timeframe) {
261✔
185
        std::cerr << "Error: Cannot register a nullptr TimeFrame for key: " << key << std::endl;
1✔
186
        return false;
1✔
187
    }
188

189
    if (_times.find(key) != _times.end()) {
260✔
190
        if (!overwrite) {
30✔
191
            std::cerr << "Error: Time key already exists in DataManager: " << key << std::endl;
26✔
192
            return false;
26✔
193
        }
194
    }
195

196
    _times[key] = std::move(timeframe);
234✔
197

198
    // Move ptr to new time frame to all data that hold
199
    for (auto const & [data_key, data]: _data) {
469✔
200
        if (_time_frames.find(data_key) != _time_frames.end()) {
235✔
201
            auto time_key = _time_frames[data_key];
235✔
202
            if (time_key == key) {
235✔
203
                std::visit([this, timeframe](auto & x) {
8✔
204
                    x->setTimeFrame(timeframe);
4✔
205
                },
4✔
206
                           data);
207
            }
208
        }
235✔
209
    }
210

211
    return true;
234✔
212
}
213

214
std::shared_ptr<TimeFrame> DataManager::getTime() {
8✔
215
    return _times[TimeKey("time")];
8✔
216
};
217

218
std::shared_ptr<TimeFrame> DataManager::getTime(TimeKey const & key) {
190✔
219
    if (_times.find(key) != _times.end()) {
190✔
220
        return _times[key];
188✔
221
    }
222
    return nullptr;
2✔
223
};
224

225
TimeIndexAndFrame DataManager::getCurrentIndexAndFrame(TimeKey const & key) {
×
226
    if (_times.find(key) != _times.end()) {
×
227
        return {TimeFrameIndex(_current_time), _times[key]};
×
228
    }
229
    return {TimeFrameIndex(_current_time), nullptr};
×
230
}
×
231

232
bool DataManager::removeTime(TimeKey const & key) {
×
233
    if (_times.find(key) == _times.end()) {
×
234
        std::cerr << "Error: could not find time key in DataManager: " << key << std::endl;
×
235
        return false;
×
236
    }
237

238
    auto it = _times.find(key);
×
239
    _times.erase(it);
×
240
    return true;
×
241
}
242

243
bool DataManager::setTimeKey(std::string const & data_key, TimeKey const & time_key) {
920✔
244
    if (_data.find(data_key) == _data.end()) {
920✔
245
        std::cerr << "Error: Data key not found in DataManager: " << data_key << std::endl;
3✔
246
        return false;
3✔
247
    }
248

249
    if (_times.find(time_key) == _times.end()) {
917✔
250
        std::cerr << "Error: Time key not found in DataManager: " << time_key << std::endl;
1✔
251
        return false;
1✔
252
    }
253

254
    _time_frames[data_key] = time_key;
916✔
255

256
    if (_data.find(data_key) != _data.end()) {
916✔
257
        auto data = _data[data_key];
916✔
258
        std::visit([this, time_key](auto & x) {
1,832✔
259
            x->setTimeFrame(this->_times[time_key]);
916✔
260
        },
916✔
261
                   data);
262
    }
916✔
263
    return true;
916✔
264
}
265

266
TimeKey DataManager::getTimeKey(std::string const & data_key) {
498✔
267
    // check if data_key exists
268
    if (_data.find(data_key) == _data.end()) {
498✔
269
        std::cerr << "Error: Data key not found in DataManager: " << data_key << std::endl;
1✔
270
        return TimeKey("");
1✔
271
    }
272

273
    // check if data key has time frame
274
    if (_time_frames.find(data_key) == _time_frames.end()) {
497✔
275
        std::cerr << "Error: Data key "
276
                  << data_key
277
                  << " exists, but not assigned to a TimeFrame" << std::endl;
×
278
        return TimeKey("");
×
279
    }
280

281
    return _time_frames[data_key];
497✔
282
}
283

284
std::vector<TimeKey> DataManager::getTimeFrameKeys() {
19✔
285
    std::vector<TimeKey> keys;
19✔
286
    keys.reserve(_times.size());
19✔
287
    for (auto const & [key, value]: _times) {
46✔
288

289
        keys.push_back(key);
27✔
290
    }
291
    return keys;
19✔
292
}
×
293

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

296
    int id = -1;
7✔
297

298
    if (_data.find(key) != _data.end()) {
7✔
299
        auto data = _data[key];
6✔
300

301
        id = std::visit([callback](auto & x) {
12✔
302
            return x.get()->addObserver(callback);
6✔
303
        },
304
                        data);
305
    }
6✔
306

307
    return id;
7✔
308
}
309

310
bool DataManager::removeCallbackFromData(std::string const & key, int callback_id) {
4✔
311
    if (_data.find(key) != _data.end()) {
4✔
312
        auto data = _data[key];
3✔
313

314
        std::visit([callback_id](auto & x) {
6✔
315
            x.get()->removeObserver(callback_id);
3✔
316
        },
3✔
317
                   data);
318

319
        return true;
3✔
320
    }
3✔
321

322
    return false;
1✔
323
}
324

325
void DataManager::addObserver(ObserverCallback callback) {
41✔
326
    _observers.push_back(std::move(callback));
41✔
327
}
41✔
328

329
void DataManager::_notifyObservers() {
677✔
330
    for (auto & observer: _observers) {
718✔
331
        observer();
41✔
332
    }
333
}
677✔
334

335
// ===== Table Registry accessors =====
336
TableRegistry * DataManager::getTableRegistry() {
47✔
337
    return _table_registry.get();
47✔
338
}
339

340
TableRegistry const * DataManager::getTableRegistry() const {
×
341
    return _table_registry.get();
×
342
}
343

344
// ===== Table observer channel =====
345
int DataManager::addTableObserver(TableObserver callback) {
×
346
    if (!callback) return -1;
×
347
    int id = _next_table_observer_id++;
×
348
    _table_observers[id] = std::move(callback);
×
349
    return id;
×
350
}
351

352
bool DataManager::removeTableObserver(int callback_id) {
×
353
    return _table_observers.erase(callback_id) > 0;
×
354
}
355

356
void DataManager::notifyTableObservers(TableEvent const & ev) {
43✔
357
    for (auto const & [id, cb]: _table_observers) {
43✔
358
        (void) id;
359
        cb(ev);
×
360
    }
361
}
43✔
362

363
// Provide C-style bridge for TableRegistry to call
364
void DataManager__NotifyTableObservers(DataManager & dm, TableEvent const & ev) {
43✔
365
    dm.notifyTableObservers(ev);
43✔
366
}
43✔
367

368
std::vector<std::string> DataManager::getAllKeys() {
71✔
369
    std::vector<std::string> keys;
71✔
370
    keys.reserve(_data.size());
71✔
371
    for (auto const & [key, value]: _data) {
410✔
372

373
        keys.push_back(key);
339✔
374
    }
375
    return keys;
71✔
376
}
×
377

378
std::optional<DataTypeVariant> DataManager::getDataVariant(std::string const & key) {
74✔
379
    if (_data.find(key) != _data.end()) {
74✔
380
        return _data[key];
74✔
381
    }
382
    return std::nullopt;
×
383
}
384

385
void DataManager::setData(std::string const & key, DataTypeVariant data, TimeKey const & time_key) {
75✔
386
    _data[key] = data;
75✔
387
    setTimeKey(key, time_key);
75✔
388
    _notifyObservers();
75✔
389
}
75✔
390

391
bool DataManager::deleteData(std::string const & key) {
14✔
392
    // Check if the key exists
393
    if (_data.find(key) == _data.end()) {
14✔
UNCOV
394
        std::cerr << "Error: Data key not found in DataManager: " << key << std::endl;
×
UNCOV
395
        return false;
×
396
    }
397

398
    // Remove the time frame mapping if it exists
399
    _time_frames.erase(key);
14✔
400
    
401
    // Remove the data from storage
402
    _data.erase(key);
14✔
403
    
404
    // Notify all observers that data has changed
405
    _notifyObservers();
14✔
406
    
407
    std::cout << "DataManager: Successfully deleted data with key: " << key << std::endl;
14✔
408
    return true;
14✔
409
}
410

411
std::optional<std::string> processFilePath(
12✔
412
        std::string const & file_path,
413
        std::filesystem::path const & base_path) {
414
    std::filesystem::path full_path = file_path;
12✔
415

416
    // Check for wildcard character
417
    if (file_path.find('*') != std::string::npos) {
12✔
418
        // Convert wildcard pattern to regex
UNCOV
419
        std::string const pattern = std::regex_replace(full_path.string(), std::regex("\\*"), ".*");
×
UNCOV
420
        std::regex const regex_pattern(pattern);
×
421

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

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

UNCOV
456
void checkOptionalFields(json const & item, std::vector<std::string> const & optionalFields) {
×
UNCOV
457
    for (auto const & field: optionalFields) {
×
UNCOV
458
        if (!item.contains(field)) {
×
UNCOV
459
            std::cout << "Warning: Optional field \"" << field << "\" is missing in JSON item." << std::endl;
×
460
        }
461
    }
UNCOV
462
}
×
463

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

478
std::vector<DataInfo> load_data_from_json_config(DataManager * dm, json const & j, std::filesystem::path const & base_path) {
71✔
479
    std::vector<DataInfo> data_info_list;
71✔
480
    // Create factory for plugin system
481
    ConcreteDataFactory factory;
71✔
482

483
    // Iterate through JSON array
484
    for (auto const & item: j) {
142✔
485

486
        // Skip transformation objects - they will be processed separately
487
        if (item.contains("transformations")) {
71✔
488
            continue;
59✔
489
        }
490

491
        if (!checkRequiredFields(item, {"data_type", "name", "filepath"})) {
36✔
UNCOV
492
            continue;// Exit if any required field is missing
×
493
        }
494

495
        std::string const data_type_str = item["data_type"];
12✔
496
        auto const data_type = stringToDataType(data_type_str);
12✔
497
        if (data_type == DM_DataType::Unknown) {
12✔
498
            std::cout << "Unknown data type: " << data_type_str << std::endl;
×
499
            continue;
×
500
        }
501

502
        std::string const name = item["name"];
12✔
503

504
        auto file_exists = processFilePath(item["filepath"], base_path);
12✔
505
        if (!file_exists) {
12✔
506
            std::cerr << "File does not exist: " << item["filepath"] << std::endl;
6✔
507
            continue;
6✔
508
        }
509

510
        std::string const file_path = file_exists.value();
6✔
511

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

UNCOV
539
                auto point_data = load_into_PointData(file_path, item);
×
540

541
                // Attach identity context and generate EntityIds
UNCOV
542
                if (point_data) {
×
543
                    point_data->setIdentityContext(name, dm->getEntityRegistry());
×
544
                    point_data->rebuildAllEntityIds();
×
545
                }
546

UNCOV
547
                dm->setData<PointData>(name, point_data, TimeKey("time"));
×
548

549
                std::string const color = item.value("color", "#0000FF");
×
UNCOV
550
                data_info_list.push_back({name, "PointData", color});
×
UNCOV
551
                break;
×
UNCOV
552
            }
×
553
            case DM_DataType::Mask: {
1✔
554

555
                // Try registry system first, then fallback to legacy
556
                if (tryRegistryThenLegacyLoad(dm, file_path, data_type, item, name, data_info_list, &factory)) {
1✔
557
                    break;// Successfully loaded with plugin
1✔
558
                }
559

560
                // Legacy loading fallback
UNCOV
561
                auto mask_data = load_into_MaskData(file_path, item);
×
562

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

UNCOV
566
                data_info_list.push_back({name, "MaskData", color});
×
567

UNCOV
568
                break;
×
UNCOV
569
            }
×
570
            case DM_DataType::Line: {
3✔
571

572
                // Try registry system first, then fallback to legacy
573
                if (tryRegistryThenLegacyLoad(dm, file_path, data_type, item, name, data_info_list, &factory)) {
3✔
574
                    break;// Successfully loaded with plugin
2✔
575
                }
576

577
                // Legacy loading fallback
578
                auto line_data = load_into_LineData(file_path, item);
1✔
579

580
                // Attach identity context and generate EntityIds
581
                if (line_data) {
1✔
582
                    line_data->setIdentityContext(name, dm->getEntityRegistry());
1✔
583
                    line_data->rebuildAllEntityIds();
1✔
584
                }
585

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

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

590
                data_info_list.push_back({name, "LineData", color});
3✔
591

592
                break;
1✔
593
            }
1✔
594
            case DM_DataType::Analog: {
1✔
595

596
                auto analog_time_series = load_into_AnalogTimeSeries(file_path, item);
1✔
597

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

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

603
                    if (item.contains("clock")) {
1✔
604
                        std::string const clock_str = item["clock"];
×
UNCOV
605
                        auto const clock = TimeKey(clock_str);
×
606
                        dm->setTimeKey(channel_name, clock);
×
607
                    }
×
608
                }
1✔
609
                break;
1✔
610
            }
1✔
611
            case DM_DataType::DigitalEvent: {
×
612

613
                auto digital_event_series = load_into_DigitalEventSeries(file_path, item);
×
614

UNCOV
615
                for (size_t channel = 0; channel < digital_event_series.size(); channel++) {
×
UNCOV
616
                    std::string const channel_name = name + "_" + std::to_string(channel);
×
617

618
                    // Attach identity context and generate EntityIds
UNCOV
619
                    if (digital_event_series[channel]) {
×
UNCOV
620
                        digital_event_series[channel]->setIdentityContext(channel_name, dm->getEntityRegistry());
×
UNCOV
621
                        digital_event_series[channel]->rebuildAllEntityIds();
×
622
                    }
623

UNCOV
624
                    dm->setData<DigitalEventSeries>(channel_name, digital_event_series[channel], TimeKey("time"));
×
625

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

636
                auto digital_interval_series = load_into_DigitalIntervalSeries(file_path, item);
1✔
637
                if (digital_interval_series) {
1✔
638
                    digital_interval_series->setIdentityContext(name, dm->getEntityRegistry());
1✔
639
                    digital_interval_series->rebuildAllEntityIds();
1✔
640
                }
641
                dm->setData<DigitalIntervalSeries>(name, digital_interval_series, TimeKey("time"));
1✔
642

643
                break;
1✔
644
            }
1✔
UNCOV
645
            case DM_DataType::Tensor: {
×
646

UNCOV
647
                if (item["format"] == "numpy") {
×
648

649
                    TensorData tensor_data;
×
650
                    loadNpyToTensorData(file_path, tensor_data);
×
651

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

UNCOV
654
                } else {
×
UNCOV
655
                    std::cout << "Format " << item["format"] << " not found for " << name << std::endl;
×
656
                }
657
                break;
×
658
            }
659
            case DM_DataType::Time: {
×
660

661
                if (item["format"] == "uint16") {
×
662

663
                    int const channel = item["channel"];
×
664
                    std::string const transition = item["transition"];
×
665

UNCOV
666
                    int const header_size = item.value("header_size", 0);
×
667

UNCOV
668
                    auto opts = Loader::BinaryAnalogOptions{.file_path = file_path,
×
669
                                                            .header_size_bytes = static_cast<size_t>(header_size)};
×
UNCOV
670
                    auto data = Loader::readBinaryFile<uint16_t>(opts);
×
671

672
                    auto digital_data = Loader::extractDigitalData(data, channel);
×
673
                    auto events = Loader::extractEvents(digital_data, transition);
×
674

675
                    // convert to int with std::transform
676
                    std::vector<int> events_int;
×
UNCOV
677
                    events_int.reserve(events.size());
×
678
                    for (auto e: events) {
×
UNCOV
679
                        events_int.push_back(static_cast<int>(e));
×
680
                    }
681
                    std::cout << "Loaded " << events_int.size() << " events for " << name << std::endl;
×
682

UNCOV
683
                    auto timeframe = std::make_shared<TimeFrame>(events_int);
×
684
                    dm->setTime(TimeKey(name), timeframe, true);
×
UNCOV
685
                }
×
686

687
                if (item["format"] == "uint16_length") {
×
688

UNCOV
689
                    int const header_size = item.value("header_size", 0);
×
690

691
                    auto opts = Loader::BinaryAnalogOptions{.file_path = file_path,
×
692
                                                            .header_size_bytes = static_cast<size_t>(header_size)};
×
693
                    auto data = Loader::readBinaryFile<uint16_t>(opts);
×
694

UNCOV
695
                    std::vector<int> t(data.size());
×
696
                    std::iota(std::begin(t), std::end(t), 0);
×
697

698
                    std::cout << "Total of " << t.size() << " timestamps for " << name << std::endl;
×
699

700
                    auto timeframe = std::make_shared<TimeFrame>(t);
×
UNCOV
701
                    dm->setTime(TimeKey(name), timeframe, true);
×
UNCOV
702
                }
×
703

704
                if (item["format"] == "filename") {
×
705

706
                    // Get required parameters
707
                    std::string const folder_path = file_path;// file path is required argument
×
708
                    std::string const regex_pattern = item["regex_pattern"];
×
709

710
                    // Get optional parameters with defaults
UNCOV
711
                    std::string const file_extension = item.value("file_extension", "");
×
712
                    std::string const mode_str = item.value("mode", "found_values");
×
713
                    bool const sort_ascending = item.value("sort_ascending", true);
×
714

715
                    // Convert mode string to enum
716
                    FilenameTimeFrameMode mode = FilenameTimeFrameMode::FOUND_VALUES;
×
UNCOV
717
                    if (mode_str == "zero_to_max") {
×
UNCOV
718
                        mode = FilenameTimeFrameMode::ZERO_TO_MAX;
×
719
                    } else if (mode_str == "min_to_max") {
×
UNCOV
720
                        mode = FilenameTimeFrameMode::MIN_TO_MAX;
×
721
                    }
722

723
                    // Create options
724
                    FilenameTimeFrameOptions options;
×
725
                    options.folder_path = folder_path;
×
726
                    options.file_extension = file_extension;
×
727
                    options.regex_pattern = regex_pattern;
×
UNCOV
728
                    options.mode = mode;
×
729
                    options.sort_ascending = sort_ascending;
×
730

731
                    // Create TimeFrame from filenames
732
                    auto timeframe = createTimeFrameFromFilenames(options);
×
733
                    if (timeframe) {
×
UNCOV
734
                        dm->setTime(TimeKey(name), timeframe, true);
×
735
                        std::cout << "Created TimeFrame '" << name << "' from filenames in "
UNCOV
736
                                  << folder_path << std::endl;
×
737
                    } else {
738
                        std::cerr << "Error: Failed to create TimeFrame from filenames for "
UNCOV
739
                                  << name << std::endl;
×
740
                    }
UNCOV
741
                }
×
UNCOV
742
                break;
×
743
            }
UNCOV
744
            default:
×
UNCOV
745
                std::cout << "Unsupported data type: " << data_type_str << std::endl;
×
UNCOV
746
                continue;
×
UNCOV
747
        }
×
748
        if (item.contains("clock")) {
6✔
749
            std::string clock_str = item["clock"];
×
UNCOV
750
            auto clock = TimeKey(clock_str);
×
UNCOV
751
            std::cout << "Setting time for " << name << " to " << clock << std::endl;
×
UNCOV
752
            dm->setTimeKey(name, clock);
×
UNCOV
753
        }
×
754
    }
24✔
755

756
    // Process all transformation objects found in the JSON array
757
    for (auto const & item: j) {
142✔
758
        if (item.contains("transformations")) {
71✔
759
            std::cout << "Found transformations section, executing pipeline..." << std::endl;
59✔
760

761
            try {
762
                // Create registry and pipeline with proper constructors
763
                auto registry = std::make_unique<TransformRegistry>();
59✔
764
                TransformPipeline pipeline(dm, registry.get());
59✔
765

766
                // Load the pipeline configuration from JSON
767
                if (!pipeline.loadFromJson(item["transformations"])) {
59✔
768
                    std::cerr << "Failed to load pipeline configuration from JSON" << std::endl;
×
UNCOV
769
                    continue;
×
770
                }
771

772
                // Execute the pipeline with a progress callback
773
                auto result = pipeline.execute([](int step_index, std::string const & step_name, int step_progress, int overall_progress) {
118✔
774
                    std::cout << "Step " << step_index << " ('" << step_name << "'): "
324✔
775
                              << step_progress << "% (Overall: " << overall_progress << "%)" << std::endl;
324✔
776
                });
118✔
777

778
                if (result.success) {
59✔
779
                    std::cout << "Pipeline executed successfully!" << std::endl;
59✔
780
                    std::cout << "Steps completed: " << result.steps_completed << "/" << result.total_steps << std::endl;
59✔
781
                    std::cout << "Total execution time: " << result.total_execution_time_ms << " ms" << std::endl;
59✔
782
                } else {
UNCOV
783
                    std::cerr << "Pipeline execution failed: " << result.error_message << std::endl;
×
784
                }
785

786
            } catch (std::exception const & e) {
59✔
UNCOV
787
                std::cerr << "Exception during pipeline execution: " << e.what() << std::endl;
×
UNCOV
788
            }
×
789
        }
790
    }
791

792
    return data_info_list;
71✔
793
}
74✔
794

795
std::vector<DataInfo> load_data_from_json_config(DataManager * dm, std::string const & json_filepath) {
71✔
796
    // Open JSON file
797
    std::ifstream ifs(json_filepath);
71✔
798
    if (!ifs.is_open()) {
71✔
UNCOV
799
        std::cerr << "Failed to open JSON file: " << json_filepath << std::endl;
×
UNCOV
800
        return {};
×
801
    }
802

803
    // Parse JSON
804
    json j;
71✔
805
    ifs >> j;
71✔
806

807
    // get base path of filepath
808
    std::filesystem::path const base_path = std::filesystem::path(json_filepath).parent_path();
71✔
809
    return load_data_from_json_config(dm, j, base_path);
71✔
810
}
71✔
811

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

849
std::string convert_data_type_to_string(DM_DataType type) {
493✔
850
    switch (type) {
493✔
851
        case DM_DataType::Video:
58✔
852
            return "video";
174✔
UNCOV
853
        case DM_DataType::Images:
×
UNCOV
854
            return "images";
×
855
        case DM_DataType::Points:
92✔
856
            return "points";
276✔
UNCOV
857
        case DM_DataType::Mask:
×
UNCOV
858
            return "mask";
×
859
        case DM_DataType::Line:
84✔
860
            return "line";
252✔
861
        case DM_DataType::Analog:
96✔
862
            return "analog";
288✔
863
        case DM_DataType::DigitalEvent:
107✔
864
            return "digital_event";
321✔
865
        case DM_DataType::DigitalInterval:
56✔
866
            return "digital_interval";
168✔
UNCOV
867
        case DM_DataType::Tensor:
×
UNCOV
868
            return "tensor";
×
UNCOV
869
        case DM_DataType::Time:
×
UNCOV
870
            return "time";
×
UNCOV
871
        default:
×
UNCOV
872
            return "unknown";
×
873
    }
874
}
875

876
template std::shared_ptr<AnalogTimeSeries> DataManager::getData<AnalogTimeSeries>(std::string const & key);
877
template void DataManager::setData<AnalogTimeSeries>(std::string const & key, TimeKey const & time_key);
878
template void DataManager::setData<AnalogTimeSeries>(std::string const & key, std::shared_ptr<AnalogTimeSeries> data, TimeKey const & time_key);
879

880
template std::shared_ptr<DigitalEventSeries> DataManager::getData<DigitalEventSeries>(std::string const & key);
881
template void DataManager::setData<DigitalEventSeries>(std::string const & key, TimeKey const & time_key);
882
template void DataManager::setData<DigitalEventSeries>(std::string const & key, std::shared_ptr<DigitalEventSeries> data, TimeKey const & time_key);
883

884
template std::shared_ptr<DigitalIntervalSeries> DataManager::getData<DigitalIntervalSeries>(std::string const & key);
885
template void DataManager::setData<DigitalIntervalSeries>(std::string const & key, TimeKey const & time_key);
886
template void DataManager::setData<DigitalIntervalSeries>(std::string const & key, std::shared_ptr<DigitalIntervalSeries> data, TimeKey const & time_key);
887

888
template std::shared_ptr<LineData> DataManager::getData<LineData>(std::string const & key);
889
template void DataManager::setData<LineData>(std::string const & key, TimeKey const & time_key);
890
template void DataManager::setData<LineData>(std::string const & key, std::shared_ptr<LineData> data, TimeKey const & time_key);
891

892
template std::shared_ptr<MaskData> DataManager::getData<MaskData>(std::string const & key);
893
template void DataManager::setData<MaskData>(std::string const & key, TimeKey const & time_key);
894
template void DataManager::setData<MaskData>(std::string const & key, std::shared_ptr<MaskData> data, TimeKey const & time_key);
895

896
template std::shared_ptr<MediaData> DataManager::getData<MediaData>(std::string const & key);
897

898
template std::shared_ptr<PointData> DataManager::getData<PointData>(std::string const & key);
899
template void DataManager::setData<PointData>(std::string const & key, TimeKey const & time_key);
900
template void DataManager::setData<PointData>(std::string const & key, std::shared_ptr<PointData> data, TimeKey const & time_key);
901

902
template std::shared_ptr<TensorData> DataManager::getData<TensorData>(std::string const & key);
903
template void DataManager::setData<TensorData>(std::string const & key, TimeKey const & time_key);
904
template void DataManager::setData<TensorData>(std::string const & key, std::shared_ptr<TensorData> data, TimeKey const & time_key);
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