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

paulmthompson / WhiskerToolbox / 18840223125

27 Oct 2025 12:01PM UTC coverage: 73.058% (+0.2%) from 72.822%
18840223125

push

github

paulmthompson
fix failing tests from table designer redesign

69 of 74 new or added lines in 2 files covered. (93.24%)

669 existing lines in 10 files now uncovered.

56029 of 76691 relevant lines covered (73.06%)

45039.63 hits per line

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

64.36
/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/EntityGroupManager.hpp"
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
    // Extract format if available
66
    if (item.contains("format")) {
4✔
67
        std::string const format = item["format"];
4✔
68

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

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

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

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

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

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

103
                            std::string const color = item.value("color", "0000FF");
1✔
104
                            data_info_list.push_back({name, "MaskData", color});
3✔
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() {
489✔
126
    _times[TimeKey("time")] = std::make_shared<TimeFrame>();
489✔
127
    _data["media"] = std::make_shared<EmptyMediaData>();
1,467✔
128

129
    setTimeKey("media", TimeKey("time"));
1,467✔
130
    _output_path = std::filesystem::current_path();
489✔
131

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

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

138
    // Initialize EntityGroupManager
139
    _entity_group_manager = std::make_unique<EntityGroupManager>();
489✔
140

141
    // Register all available loaders
142
    static bool loaders_registered = false;
143
    if (!loaders_registered) {
489✔
144
        registerAllLoaders();
268✔
145
        loaders_registered = true;
268✔
146
    }
147
}
489✔
148

149
DataManager::~DataManager() = default;
489✔
150

151
void DataManager::reset() {
1✔
152
    std::cout << "DataManager: Resetting to initial state..." << std::endl;
1✔
153

154
    // Clear all data objects except media (which we'll reset)
155
    _data.clear();
1✔
156

157
    // Reset media to a fresh empty MediaData object
158
    _data["media"] = std::make_shared<EmptyMediaData>();
3✔
159

160
    // Clear all TimeFrame objects except the default "time" frame
161
    _times.clear();
1✔
162

163
    // Recreate the default "time" TimeFrame
164
    _times[TimeKey("time")] = std::make_shared<TimeFrame>();
1✔
165

166
    // Clear all data-to-timeframe mappings and recreate the default media mapping
167
    _time_frames.clear();
1✔
168
    setTimeKey("media", TimeKey("time"));
3✔
169

170

171
    // Reset current time
172
    _current_time = 0;
1✔
173

174
    // Notify observers that the state has changed
175
    _notifyObservers();
1✔
176

177
    std::cout << "DataManager: Reset complete. Default 'time' frame and 'media' data restored." << std::endl;
1✔
178

179
    // Reset entity registry for a new session context
180
    if (_entity_registry) {
1✔
181
        _entity_registry->clear();
1✔
182
    }
183

184
    // Reset entity group manager for a new session context
185
    if (_entity_group_manager) {
1✔
186
        _entity_group_manager->clear();
1✔
187
    }
188
}
1✔
189

190
bool DataManager::setTime(TimeKey const & key, std::shared_ptr<TimeFrame> timeframe, bool overwrite) {
511✔
191

192
    if (!timeframe) {
511✔
193
        std::cerr << "Error: Cannot register a nullptr TimeFrame for key: " << key << std::endl;
1✔
194
        return false;
1✔
195
    }
196

197
    if (_times.find(key) != _times.end()) {
510✔
198
        if (!overwrite) {
25✔
199
            std::cerr << "Error: Time key already exists in DataManager: " << key << std::endl;
20✔
200
            return false;
20✔
201
        }
202
    }
203

204
    _times[key] = std::move(timeframe);
490✔
205

206
    // Move ptr to new time frame to all data that hold
207
    for (auto const & [data_key, data]: _data) {
999✔
208
        if (_time_frames.find(data_key) != _time_frames.end()) {
509✔
209
            auto time_key = _time_frames[data_key];
509✔
210
            if (time_key == key) {
509✔
211
                std::visit([this, key](auto & x) {
54✔
212
                    x->setTimeFrame(_times[key]);
27✔
213
                },
27✔
214
                           data);
215
            }
216
        }
509✔
217
    }
218

219
    return true;
490✔
220
}
221

222
std::shared_ptr<TimeFrame> DataManager::getTime() {
7✔
223
    return _times[TimeKey("time")];
7✔
224
};
225

226
std::shared_ptr<TimeFrame> DataManager::getTime(TimeKey const & key) {
667✔
227
    if (_times.find(key) != _times.end()) {
667✔
228
        return _times[key];
665✔
229
    }
230
    return nullptr;
2✔
231
};
232

233
TimeIndexAndFrame DataManager::getCurrentIndexAndFrame(TimeKey const & key) {
8✔
234
    if (_times.find(key) != _times.end()) {
8✔
235
        return {TimeFrameIndex(_current_time), _times[key]};
8✔
236
    }
237
    return {TimeFrameIndex(_current_time), nullptr};
×
238
}
8✔
239

240
bool DataManager::removeTime(TimeKey const & key) {
22✔
241
    if (_times.find(key) == _times.end()) {
22✔
242
        std::cerr << "Error: could not find time key in DataManager: " << key << std::endl;
×
243
        return false;
×
244
    }
245

246
    auto it = _times.find(key);
22✔
247
    _times.erase(it);
22✔
248
    return true;
22✔
249
}
250

251
bool DataManager::setTimeKey(std::string const & data_key, TimeKey const & time_key) {
1,667✔
252
    if (_data.find(data_key) == _data.end()) {
1,667✔
253
        std::cerr << "Error: Data key not found in DataManager: " << data_key << std::endl;
3✔
254
        return false;
3✔
255
    }
256

257
    if (_times.find(time_key) == _times.end()) {
1,664✔
258
        std::cerr << "Error: Time key not found in DataManager: " << time_key << std::endl;
2✔
259
        return false;
2✔
260
    }
261

262
    _time_frames[data_key] = time_key;
1,662✔
263

264
    if (_data.find(data_key) != _data.end()) {
1,662✔
265
        auto data = _data[data_key];
1,662✔
266
        std::visit([this, time_key](auto & x) {
3,324✔
267
            x->setTimeFrame(this->_times[time_key]);
1,662✔
268
        },
1,662✔
269
                   data);
270
    }
1,662✔
271
    return true;
1,662✔
272
}
273

274
TimeKey DataManager::getTimeKey(std::string const & data_key) {
1,017✔
275
    // check if data_key exists
276
    if (_data.find(data_key) == _data.end()) {
1,017✔
277
        std::cerr << "Error: Data key not found in DataManager: " << data_key << std::endl;
1✔
278
        return TimeKey("");
1✔
279
    }
280

281
    // check if data key has time frame
282
    if (_time_frames.find(data_key) == _time_frames.end()) {
1,016✔
283
        std::cerr << "Error: Data key "
284
                  << data_key
285
                  << " exists, but not assigned to a TimeFrame" << std::endl;
×
286
        return TimeKey("");
×
287
    }
288

289
    return _time_frames[data_key];
1,016✔
290
}
291

292
std::vector<TimeKey> DataManager::getTimeFrameKeys() {
158✔
293
    std::vector<TimeKey> keys;
158✔
294
    keys.reserve(_times.size());
158✔
295
    for (auto const & [key, value]: _times) {
724✔
296

297
        keys.push_back(key);
566✔
298
    }
299
    return keys;
158✔
300
}
×
301

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

304
    int id = -1;
7✔
305

306
    if (_data.find(key) != _data.end()) {
7✔
307
        auto data = _data[key];
6✔
308

309
        id = std::visit([callback](auto & x) {
12✔
310
            return x.get()->addObserver(callback);
6✔
311
        },
312
                        data);
313
    }
6✔
314

315
    return id;
7✔
316
}
317

318
bool DataManager::removeCallbackFromData(std::string const & key, int callback_id) {
4✔
319
    if (_data.find(key) != _data.end()) {
4✔
320
        auto data = _data[key];
3✔
321

322
        std::visit([callback_id](auto & x) {
6✔
323
            x.get()->removeObserver(callback_id);
3✔
324
        },
3✔
325
                   data);
326

327
        return true;
3✔
328
    }
3✔
329

330
    return false;
1✔
331
}
332

333
void DataManager::addObserver(ObserverCallback callback) {
101✔
334
    _observers.push_back(std::move(callback));
101✔
335
}
101✔
336

337
void DataManager::_notifyObservers() {
1,187✔
338
    for (auto & observer: _observers) {
1,243✔
339
        observer();
56✔
340
    }
341
}
1,187✔
342

343
// ===== Table Registry accessors =====
344
TableRegistry * DataManager::getTableRegistry() {
672✔
345
    return _table_registry.get();
672✔
346
}
347

348
TableRegistry const * DataManager::getTableRegistry() const {
×
349
    return _table_registry.get();
×
350
}
351

352
// ===== Table observer channel =====
353
int DataManager::addTableObserver(TableObserver callback) {
23✔
354
    if (!callback) return -1;
23✔
355
    int id = _next_table_observer_id++;
23✔
356
    _table_observers[id] = std::move(callback);
23✔
357
    return id;
23✔
358
}
359

360
bool DataManager::removeTableObserver(int callback_id) {
×
361
    return _table_observers.erase(callback_id) > 0;
×
362
}
363

364
void DataManager::notifyTableObservers(TableEvent const & ev) {
90✔
365
    for (auto const & [id, cb]: _table_observers) {
131✔
366
        (void) id;
367
        cb(ev);
41✔
368
    }
369
}
90✔
370

371
// Provide C-style bridge for TableRegistry to call
372
void DataManager__NotifyTableObservers(DataManager & dm, TableEvent const & ev) {
90✔
373
    dm.notifyTableObservers(ev);
90✔
374
}
90✔
375

376
std::vector<std::string> DataManager::getAllKeys() {
120✔
377
    std::vector<std::string> keys;
120✔
378
    keys.reserve(_data.size());
120✔
379
    for (auto const & [key, value]: _data) {
710✔
380

381
        keys.push_back(key);
590✔
382
    }
383
    return keys;
120✔
384
}
×
385

386
std::optional<DataTypeVariant> DataManager::getDataVariant(std::string const & key) {
83✔
387
    if (_data.find(key) != _data.end()) {
83✔
388
        return _data[key];
83✔
389
    }
390
    return std::nullopt;
×
391
}
392

393
void DataManager::setData(std::string const & key, DataTypeVariant data, TimeKey const & time_key) {
84✔
394
    // Loop through all _data. If shared_ptr data is already in _data, return
395
    for (auto const & [existing_key, existing_variant]: _data) {
347✔
396
        // Safely compare only when the variant alternatives match; avoid std::bad_variant_access
397
        bool found = std::visit([
526✔
398
            &data
399
        ](auto const & existing_ptr) -> bool {
400
            using PtrT = std::decay_t<decltype(existing_ptr)>;
401
            if (auto const * incoming_ptr = std::get_if<PtrT>(&data)) {
263✔
402
                return existing_ptr == *incoming_ptr; // shared_ptr comparison (same pointee address)
127✔
403
            }
404
            return false;
136✔
405
        }, existing_variant);
263✔
406

407
        if (found) {
263✔
408
            std::cerr << "Data with key '" << existing_key
409
                      << "' already exists; not setting duplicate under key '" << key << "'."
×
410
                      << std::endl;
×
411
            return; // Data already exists, do not set again
×
412
        }
413
    }
414

415
    _data[key] = data;
84✔
416
    setTimeKey(key, time_key);
84✔
417

418
    if (std::holds_alternative<std::shared_ptr<LineData>>(_data[key])) {
84✔
419
        std::get<std::shared_ptr<LineData>>(_data[key])->setIdentityContext(key, getEntityRegistry());
21✔
420
        std::get<std::shared_ptr<LineData>>(_data[key])->rebuildAllEntityIds();
21✔
421
    } else if (std::holds_alternative<std::shared_ptr<PointData>>(_data[key])) {
63✔
422
        std::get<std::shared_ptr<PointData>>(_data[key])->setIdentityContext(key, getEntityRegistry());
7✔
423
        std::get<std::shared_ptr<PointData>>(_data[key])->rebuildAllEntityIds();
7✔
424
    } else if (std::holds_alternative<std::shared_ptr<DigitalEventSeries>>(_data[key])) {
56✔
425
        std::get<std::shared_ptr<DigitalEventSeries>>(_data[key])->setIdentityContext(key, getEntityRegistry());
4✔
426
        std::get<std::shared_ptr<DigitalEventSeries>>(_data[key])->rebuildAllEntityIds();
4✔
427
    } else if (std::holds_alternative<std::shared_ptr<DigitalIntervalSeries>>(_data[key])) {
52✔
428
        std::get<std::shared_ptr<DigitalIntervalSeries>>(_data[key])->setIdentityContext(key, getEntityRegistry());
14✔
429
        std::get<std::shared_ptr<DigitalIntervalSeries>>(_data[key])->rebuildAllEntityIds();
14✔
430
    } else if (std::holds_alternative<std::shared_ptr<MaskData>>(_data[key])) {
38✔
431
        std::get<std::shared_ptr<MaskData>>(_data[key])->setIdentityContext(key, getEntityRegistry());
12✔
432
        std::get<std::shared_ptr<MaskData>>(_data[key])->rebuildAllEntityIds();
12✔
433
    }
434

435
    _notifyObservers();
84✔
436
}
437

438
bool DataManager::deleteData(std::string const & key) {
15✔
439
    // Check if the key exists
440
    if (_data.find(key) == _data.end()) {
15✔
441
        std::cerr << "Error: Data key not found in DataManager: " << key << std::endl;
×
442
        return false;
×
443
    }
444

445
    // Remove the time frame mapping if it exists
446
    _time_frames.erase(key);
15✔
447

448
    // Remove the data from storage
449
    _data.erase(key);
15✔
450

451
    // Notify all observers that data has changed
452
    _notifyObservers();
15✔
453

454
    std::cout << "DataManager: Successfully deleted data with key: " << key << std::endl;
15✔
455
    return true;
15✔
456
}
457

458
std::optional<std::string> processFilePath(
14✔
459
        std::string const & file_path,
460
        std::filesystem::path const & base_path) {
461
    std::filesystem::path full_path = file_path;
14✔
462

463
    // Check for wildcard character
464
    if (file_path.find('*') != std::string::npos) {
14✔
465
        // Convert wildcard pattern to regex
466
        std::string const pattern = std::regex_replace(full_path.string(), std::regex("\\*"), ".*");
×
467
        std::regex const regex_pattern(pattern);
×
468

469
        // Iterate through the directory to find matching files
470
        for (auto const & entry: std::filesystem::directory_iterator(base_path)) {
×
471
            std::cout << "Checking " << entry.path().string() << " with full path " << full_path << std::endl;
×
472
            if (std::regex_match(entry.path().string(), regex_pattern)) {
×
473
                std::cout << "Loading file " << entry.path().string() << std::endl;
×
474
                return entry.path().string();
×
475
            }
476
        }
×
477
        return std::nullopt;
×
478
    } else {
×
479
        // Check if the file path is relative
480
        if (!std::filesystem::path(file_path).is_absolute()) {
14✔
481
            full_path = base_path / file_path;
2✔
482
        }
483
        // Check for the presence of the file
484
        if (std::filesystem::exists(full_path)) {
14✔
485
            std::cout << "Loading file " << full_path.string() << std::endl;
8✔
486
            return full_path.string();
8✔
487
        } else {
488
            return std::nullopt;
6✔
489
        }
490
    }
491
}
14✔
492

493
bool checkRequiredFields(json const & item, std::vector<std::string> const & requiredFields) {
14✔
494
    for (auto const & field: requiredFields) {
56✔
495
        if (!item.contains(field)) {
42✔
496
            std::cerr << "Error: Missing required field \"" << field << "\" in JSON item." << std::endl;
×
497
            return false;
×
498
        }
499
    }
500
    return true;
14✔
501
}
502

503
void checkOptionalFields(json const & item, std::vector<std::string> const & optionalFields) {
×
504
    for (auto const & field: optionalFields) {
×
505
        if (!item.contains(field)) {
×
506
            std::cout << "Warning: Optional field \"" << field << "\" is missing in JSON item." << std::endl;
×
507
        }
508
    }
509
}
×
510

511
DM_DataType stringToDataType(std::string const & data_type_str) {
14✔
512
    if (data_type_str == "video") return DM_DataType::Video;
14✔
513
    if (data_type_str == "images") return DM_DataType::Images;
14✔
514
    if (data_type_str == "points") return DM_DataType::Points;
14✔
515
    if (data_type_str == "mask") return DM_DataType::Mask;
12✔
516
    if (data_type_str == "line") return DM_DataType::Line;
10✔
517
    if (data_type_str == "analog") return DM_DataType::Analog;
4✔
518
    if (data_type_str == "digital_event") return DM_DataType::DigitalEvent;
2✔
519
    if (data_type_str == "digital_interval") return DM_DataType::DigitalInterval;
2✔
520
    if (data_type_str == "tensor") return DM_DataType::Tensor;
×
521
    if (data_type_str == "time") return DM_DataType::Time;
×
522
    return DM_DataType::Unknown;
×
523
}
524

525
std::vector<DataInfo> load_data_from_json_config(DataManager * dm, json const & j, std::filesystem::path const & base_path, JsonLoadProgressCallback progress_callback) {
74✔
526
    std::vector<DataInfo> data_info_list;
74✔
527
    // Create factory for plugin system
528
    ConcreteDataFactory factory;
74✔
529

530
    // Count total items to load (excluding transformations which are processed separately)
531
    int total_items = 0;
74✔
532
    for (auto const & item: j) {
148✔
533
        if (!item.contains("transformations")) {
74✔
534
            total_items++;
14✔
535
        }
536
    }
537

538
    // Report initial progress to show the dialog immediately
539
    if (progress_callback) {
74✔
UNCOV
540
        bool should_continue = progress_callback(0, total_items, "Preparing to load data...");
×
UNCOV
541
        if (!should_continue) {
×
UNCOV
542
            std::cout << "Loading cancelled by user" << std::endl;
×
UNCOV
543
            return data_info_list;
×
544
        }
545
    }
546

547
    int current_item = 0;
74✔
548

549
    // Iterate through JSON array
550
    for (auto const & item: j) {
148✔
551

552
        // Skip transformation objects - they will be processed separately
553
        if (item.contains("transformations")) {
74✔
554
            continue;
60✔
555
        }
556

557
        if (!checkRequiredFields(item, {"data_type", "name", "filepath"})) {
42✔
UNCOV
558
            continue;// Exit if any required field is missing
×
559
        }
560

561
        std::string const data_type_str = item["data_type"];
14✔
562
        auto const data_type = stringToDataType(data_type_str);
14✔
563
        if (data_type == DM_DataType::Unknown) {
14✔
564
            std::cout << "Unknown data type: " << data_type_str << std::endl;
×
565
            continue;
×
566
        }
567

568
        std::string const name = item["name"];
14✔
569

570
        auto file_exists = processFilePath(item["filepath"], base_path);
14✔
571
        if (!file_exists) {
14✔
572
            std::cerr << "File does not exist: " << item["filepath"] << std::endl;
6✔
573
            continue;
6✔
574
        }
575

576
        std::string const file_path = file_exists.value();
8✔
577

578
        switch (data_type) {
8✔
579
            case DM_DataType::Video: {
×
UNCOV
580
                auto media_data = MediaDataFactory::loadMediaData(data_type, file_path, item);
×
581
                if (media_data) {
×
582
                    auto item_key = item.value("name", "media");
×
UNCOV
583
                    dm->setData<MediaData>(item_key, media_data, TimeKey("time"));
×
UNCOV
584
                    data_info_list.push_back({name, "VideoData", ""});
×
UNCOV
585
                } else {
×
UNCOV
586
                    std::cerr << "Failed to load video data: " << file_path << std::endl;
×
587
                }
UNCOV
588
                break;
×
UNCOV
589
            }
×
590
#ifdef ENABLE_OPENCV
UNCOV
591
            case DM_DataType::Images: {
×
UNCOV
592
                auto media_data = MediaDataFactory::loadMediaData(data_type, file_path, item);
×
UNCOV
593
                if (media_data) {
×
UNCOV
594
                    auto item_key = item.value("name", "media");
×
UNCOV
595
                    dm->setData<MediaData>(item_key, media_data, TimeKey("time"));
×
UNCOV
596
                    data_info_list.push_back({name, "ImageData", ""});
×
UNCOV
597
                } else {
×
UNCOV
598
                    std::cerr << "Failed to load image data: " << file_path << std::endl;
×
599
                }
UNCOV
600
                break;
×
UNCOV
601
            }
×
602
#endif
603
            case DM_DataType::Points: {
2✔
604

605
                // Check if this is a DLC CSV format that needs special handling
606
                if (item.contains("format") && item["format"] == "dlc_csv") {
2✔
607
                    auto multi_point_data = load_multiple_PointData_from_dlc(file_path, item);
2✔
608

609
                    // For DLC data, let Media_Window assign colors automatically via getColorForIndex
610
                    // Don't use a single color for all bodyparts
611

612
                    for (auto const & [bodypart, point_data]: multi_point_data) {
21✔
613
                        std::string const bodypart_name = name + "_" + bodypart;
19✔
614

615
                        // Attach identity context and generate EntityIds
616
                        if (point_data) {
19✔
617
                            point_data->setIdentityContext(bodypart_name, dm->getEntityRegistry());
19✔
618
                            point_data->rebuildAllEntityIds();
19✔
619
                        }
620

621
                        dm->setData<PointData>(bodypart_name, point_data, TimeKey("time"));
19✔
622
                        // Use empty color string to let Media_Window auto-assign colors
623
                        data_info_list.push_back({bodypart_name, "PointData", ""});
57✔
624
                    }
19✔
625
                } else {
2✔
626
                    // Regular point data loading
UNCOV
627
                    auto point_data = load_into_PointData(file_path, item);
×
628

629
                    // Attach identity context and generate EntityIds
UNCOV
630
                    if (point_data) {
×
631
                        point_data->setIdentityContext(name, dm->getEntityRegistry());
×
UNCOV
632
                        point_data->rebuildAllEntityIds();
×
633
                    }
634

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

UNCOV
637
                    std::string const color = item.value("color", "#0000FF");
×
638
                    data_info_list.push_back({name, "PointData", color});
×
639
                }
×
640
                break;
2✔
641
            }
642
            case DM_DataType::Mask: {
1✔
643

644
                // Try registry system first, then fallback to legacy
645
                if (tryRegistryThenLegacyLoad(dm, file_path, data_type, item, name, data_info_list, &factory)) {
1✔
646
                    break;// Successfully loaded with plugin
1✔
647
                }
648

649
                // Legacy loading fallback
UNCOV
650
                auto mask_data = load_into_MaskData(file_path, item);
×
651

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

UNCOV
655
                data_info_list.push_back({name, "MaskData", color});
×
656

UNCOV
657
                break;
×
UNCOV
658
            }
×
659
            case DM_DataType::Line: {
3✔
660

661
                // Try registry system first, then fallback to legacy
662
                if (tryRegistryThenLegacyLoad(dm, file_path, data_type, item, name, data_info_list, &factory)) {
3✔
663
                    break;// Successfully loaded with plugin
2✔
664
                }
665

666
                // Legacy loading fallback
667
                auto line_data = load_into_LineData(file_path, item);
1✔
668

669
                // Attach identity context and generate EntityIds
670
                if (line_data) {
1✔
671
                    line_data->setIdentityContext(name, dm->getEntityRegistry());
1✔
672
                    line_data->rebuildAllEntityIds();
1✔
673
                }
674

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

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

679
                data_info_list.push_back({name, "LineData", color});
3✔
680

681
                break;
1✔
682
            }
1✔
683
            case DM_DataType::Analog: {
1✔
684

685
                auto analog_time_series = load_into_AnalogTimeSeries(file_path, item);
1✔
686

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

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

692
                    if (item.contains("clock")) {
1✔
UNCOV
693
                        std::string const clock_str = item["clock"];
×
694
                        auto const clock = TimeKey(clock_str);
×
UNCOV
695
                        dm->setTimeKey(channel_name, clock);
×
696
                    }
×
697
                }
1✔
698
                break;
1✔
699
            }
1✔
700
            case DM_DataType::DigitalEvent: {
×
701

702
                auto digital_event_series = load_into_DigitalEventSeries(file_path, item);
×
703

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

707
                    // Attach identity context and generate EntityIds
UNCOV
708
                    if (digital_event_series[channel]) {
×
UNCOV
709
                        digital_event_series[channel]->setIdentityContext(channel_name, dm->getEntityRegistry());
×
UNCOV
710
                        digital_event_series[channel]->rebuildAllEntityIds();
×
711
                    }
712

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

715
                    if (item.contains("clock")) {
×
UNCOV
716
                        std::string const clock_str = item["clock"];
×
717
                        auto const clock = TimeKey(clock_str);
×
UNCOV
718
                        dm->setTimeKey(channel_name, clock);
×
719
                    }
×
720
                }
×
UNCOV
721
                break;
×
722
            }
×
723
            case DM_DataType::DigitalInterval: {
1✔
724

725
                auto digital_interval_series = load_into_DigitalIntervalSeries(file_path, item);
1✔
726
                if (digital_interval_series) {
1✔
727
                    digital_interval_series->setIdentityContext(name, dm->getEntityRegistry());
1✔
728
                    digital_interval_series->rebuildAllEntityIds();
1✔
729
                }
730
                dm->setData<DigitalIntervalSeries>(name, digital_interval_series, TimeKey("time"));
1✔
731

732
                break;
1✔
733
            }
1✔
734
            case DM_DataType::Tensor: {
×
735

736
                if (item["format"] == "numpy") {
×
737

738
                    TensorData tensor_data;
×
739
                    loadNpyToTensorData(file_path, tensor_data);
×
740

UNCOV
741
                    dm->setData<TensorData>(name, std::make_shared<TensorData>(tensor_data), TimeKey("time"));
×
742

743
                } else {
×
UNCOV
744
                    std::cout << "Format " << item["format"] << " not found for " << name << std::endl;
×
745
                }
746
                break;
×
747
            }
748
            case DM_DataType::Time: {
×
749

UNCOV
750
                if (item["format"] == "uint16") {
×
751

UNCOV
752
                    int const channel = item["channel"];
×
753
                    std::string const transition = item["transition"];
×
754

755
                    int const header_size = item.value("header_size", 0);
×
756

757
                    auto opts = Loader::BinaryAnalogOptions{.file_path = file_path,
×
UNCOV
758
                                                            .header_size_bytes = static_cast<size_t>(header_size)};
×
759
                    auto data = Loader::readBinaryFile<uint16_t>(opts);
×
760

761
                    auto digital_data = Loader::extractDigitalData(data, channel);
×
762
                    auto events = Loader::extractEvents(digital_data, transition);
×
763

764
                    // convert to int with std::transform
765
                    std::vector<int> events_int;
×
766
                    events_int.reserve(events.size());
×
UNCOV
767
                    for (auto e: events) {
×
768
                        events_int.push_back(static_cast<int>(e));
×
769
                    }
770
                    std::cout << "Loaded " << events_int.size() << " events for " << name << std::endl;
×
771

772
                    auto timeframe = std::make_shared<TimeFrame>(events_int);
×
UNCOV
773
                    dm->setTime(TimeKey(name), timeframe, true);
×
774
                }
×
775

UNCOV
776
                if (item["format"] == "uint16_length") {
×
777

778
                    int const header_size = item.value("header_size", 0);
×
779

UNCOV
780
                    auto opts = Loader::BinaryAnalogOptions{.file_path = file_path,
×
781
                                                            .header_size_bytes = static_cast<size_t>(header_size)};
×
782
                    auto data = Loader::readBinaryFile<uint16_t>(opts);
×
783

UNCOV
784
                    std::vector<int> t(data.size());
×
UNCOV
785
                    std::iota(std::begin(t), std::end(t), 0);
×
786

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

789
                    auto timeframe = std::make_shared<TimeFrame>(t);
×
790
                    dm->setTime(TimeKey(name), timeframe, true);
×
UNCOV
791
                }
×
792

UNCOV
793
                if (item["format"] == "filename") {
×
794

795
                    // Get required parameters
796
                    std::string const folder_path = file_path;// file path is required argument
×
797
                    std::string const regex_pattern = item["regex_pattern"];
×
798

799
                    // Get optional parameters with defaults
UNCOV
800
                    std::string const file_extension = item.value("file_extension", "");
×
UNCOV
801
                    std::string const mode_str = item.value("mode", "found_values");
×
802
                    bool const sort_ascending = item.value("sort_ascending", true);
×
803

804
                    // Convert mode string to enum
UNCOV
805
                    FilenameTimeFrameMode mode = FilenameTimeFrameMode::FOUND_VALUES;
×
806
                    if (mode_str == "zero_to_max") {
×
UNCOV
807
                        mode = FilenameTimeFrameMode::ZERO_TO_MAX;
×
UNCOV
808
                    } else if (mode_str == "min_to_max") {
×
809
                        mode = FilenameTimeFrameMode::MIN_TO_MAX;
×
810
                    }
811

812
                    // Create options
UNCOV
813
                    FilenameTimeFrameOptions options;
×
814
                    options.folder_path = folder_path;
×
815
                    options.file_extension = file_extension;
×
816
                    options.regex_pattern = regex_pattern;
×
817
                    options.mode = mode;
×
UNCOV
818
                    options.sort_ascending = sort_ascending;
×
819

820
                    // Create TimeFrame from filenames
821
                    auto timeframe = createTimeFrameFromFilenames(options);
×
822
                    if (timeframe) {
×
823
                        dm->setTime(TimeKey(name), timeframe, true);
×
824
                        std::cout << "Created TimeFrame '" << name << "' from filenames in "
UNCOV
825
                                  << folder_path << std::endl;
×
826
                    } else {
827
                        std::cerr << "Error: Failed to create TimeFrame from filenames for "
UNCOV
828
                                  << name << std::endl;
×
829
                    }
UNCOV
830
                }
×
UNCOV
831
                break;
×
832
            }
UNCOV
833
            default:
×
UNCOV
834
                std::cout << "Unsupported data type: " << data_type_str << std::endl;
×
UNCOV
835
                continue;
×
UNCOV
836
        }
×
837
        if (item.contains("clock")) {
8✔
838
            std::string clock_str = item["clock"];
×
839
            auto clock = TimeKey(clock_str);
×
UNCOV
840
            std::cout << "Setting time for " << name << " to " << clock << std::endl;
×
UNCOV
841
            dm->setTimeKey(name, clock);
×
UNCOV
842
        }
×
843

844
        // Increment progress counter
845
        current_item++;
8✔
846

847
        // Report progress after loading this item
848
        if (progress_callback) {
8✔
UNCOV
849
            std::string message = "Loaded " + data_type_str + ": " + name;
×
UNCOV
850
            bool should_continue = progress_callback(current_item, total_items, message);
×
UNCOV
851
            if (!should_continue) {
×
UNCOV
852
                std::cout << "Loading cancelled by user" << std::endl;
×
853
                return data_info_list;
×
854
            }
UNCOV
855
        }
×
856
    }
26✔
857

858
    // Process all transformation objects found in the JSON array
859
    for (auto const & item: j) {
148✔
860
        if (item.contains("transformations")) {
74✔
861
            std::cout << "Found transformations section, executing pipeline..." << std::endl;
60✔
862

863
            try {
864
                // Create registry and pipeline with proper constructors
865
                auto registry = std::make_unique<TransformRegistry>();
60✔
866
                TransformPipeline pipeline(dm, registry.get());
60✔
867

868
                // Load the pipeline configuration from JSON
869
                if (!pipeline.loadFromJson(item["transformations"])) {
60✔
870
                    std::cerr << "Failed to load pipeline configuration from JSON" << std::endl;
×
UNCOV
871
                    continue;
×
872
                }
873

874
                // Execute the pipeline with a progress callback
875
                auto result = pipeline.execute([](int step_index, std::string const & step_name, int step_progress, int overall_progress) {
120✔
876
                    std::cout << "Step " << step_index << " ('" << step_name << "'): "
361✔
877
                              << step_progress << "% (Overall: " << overall_progress << "%)" << std::endl;
361✔
878
                });
120✔
879

880
                if (result.success) {
60✔
881
                    std::cout << "Pipeline executed successfully!" << std::endl;
60✔
882
                    std::cout << "Steps completed: " << result.steps_completed << "/" << result.total_steps << std::endl;
60✔
883
                    std::cout << "Total execution time: " << result.total_execution_time_ms << " ms" << std::endl;
60✔
884
                } else {
UNCOV
885
                    std::cerr << "Pipeline execution failed: " << result.error_message << std::endl;
×
886
                }
887

888
            } catch (std::exception const & e) {
60✔
UNCOV
889
                std::cerr << "Exception during pipeline execution: " << e.what() << std::endl;
×
UNCOV
890
            }
×
891
        }
892
    }
893

894
    return data_info_list;
895
}
172✔
896

897
std::vector<DataInfo> load_data_from_json_config(DataManager * dm, json const & j, std::filesystem::path const & base_path) {
×
898
    // Call the version with progress callback, passing nullptr
UNCOV
899
    return load_data_from_json_config(dm, j, base_path, nullptr);
×
900
}
901

902
std::vector<DataInfo> load_data_from_json_config(DataManager * dm, std::string const & json_filepath, JsonLoadProgressCallback progress_callback) {
74✔
903
    // Open JSON file
904
    std::ifstream ifs(json_filepath);
74✔
905
    if (!ifs.is_open()) {
74✔
UNCOV
906
        std::cerr << "Failed to open JSON file: " << json_filepath << std::endl;
×
UNCOV
907
        return {};
×
908
    }
909

910
    // Parse JSON
911
    json j;
74✔
912
    ifs >> j;
74✔
913

914
    // get base path of filepath
915
    std::filesystem::path const base_path = std::filesystem::path(json_filepath).parent_path();
74✔
916
    return load_data_from_json_config(dm, j, base_path, progress_callback);
74✔
917
}
74✔
918

919
std::vector<DataInfo> load_data_from_json_config(DataManager * dm, std::string const & json_filepath) {
74✔
920
    // Call the version with progress callback, passing nullptr
921
    return load_data_from_json_config(dm, json_filepath, nullptr);
74✔
922
}
923

924
DM_DataType DataManager::getType(std::string const & key) const {
939✔
925
    auto it = _data.find(key);
939✔
926
    if (it != _data.end()) {
939✔
927
        if (std::holds_alternative<std::shared_ptr<MediaData>>(it->second)) {
936✔
928
            auto media_data = std::get<std::shared_ptr<MediaData>>(it->second);
140✔
929
            switch (media_data->getMediaType()) {
140✔
930
                case MediaData::MediaType::Video:
133✔
931
                    return DM_DataType::Video;
133✔
932
                case MediaData::MediaType::Images:
7✔
933
                    return DM_DataType::Images;
7✔
UNCOV
934
                case MediaData::MediaType::HDF5:
×
935
                    // For HDF5, we might need additional logic to determine if it's video or images
936
                    // For now, defaulting to Video (old behavior)
937
                    return DM_DataType::Video;
×
938
                default:
×
939
                    return DM_DataType::Video;// Old behavior for unknown types
×
940
            }
941
        } else if (std::holds_alternative<std::shared_ptr<PointData>>(it->second)) {
936✔
942
            return DM_DataType::Points;
114✔
943
        } else if (std::holds_alternative<std::shared_ptr<LineData>>(it->second)) {
682✔
944
            return DM_DataType::Line;
82✔
945
        } else if (std::holds_alternative<std::shared_ptr<MaskData>>(it->second)) {
600✔
946
            return DM_DataType::Mask;
16✔
947
        } else if (std::holds_alternative<std::shared_ptr<AnalogTimeSeries>>(it->second)) {
584✔
948
            return DM_DataType::Analog;
368✔
949
        } else if (std::holds_alternative<std::shared_ptr<DigitalEventSeries>>(it->second)) {
216✔
950
            return DM_DataType::DigitalEvent;
187✔
951
        } else if (std::holds_alternative<std::shared_ptr<DigitalIntervalSeries>>(it->second)) {
29✔
952
            return DM_DataType::DigitalInterval;
29✔
UNCOV
953
        } else if (std::holds_alternative<std::shared_ptr<TensorData>>(it->second)) {
×
UNCOV
954
            return DM_DataType::Tensor;
×
955
        }
UNCOV
956
        return DM_DataType::Unknown;
×
957
    }
958
    return DM_DataType::Unknown;
3✔
959
}
960

961
std::string convert_data_type_to_string(DM_DataType type) {
823✔
962
    switch (type) {
823✔
963
        case DM_DataType::Video:
88✔
964
            return "video";
264✔
UNCOV
965
        case DM_DataType::Images:
×
UNCOV
966
            return "images";
×
967
        case DM_DataType::Points:
137✔
968
            return "points";
411✔
969
        case DM_DataType::Mask:
6✔
970
            return "mask";
18✔
971
        case DM_DataType::Line:
117✔
972
            return "line";
351✔
973
        case DM_DataType::Analog:
238✔
974
            return "analog";
714✔
975
        case DM_DataType::DigitalEvent:
179✔
976
            return "digital_event";
537✔
977
        case DM_DataType::DigitalInterval:
58✔
978
            return "digital_interval";
174✔
UNCOV
979
        case DM_DataType::Tensor:
×
UNCOV
980
            return "tensor";
×
UNCOV
981
        case DM_DataType::Time:
×
UNCOV
982
            return "time";
×
UNCOV
983
        default:
×
UNCOV
984
            return "unknown";
×
985
    }
986
}
987

988
template std::shared_ptr<AnalogTimeSeries> DataManager::getData<AnalogTimeSeries>(std::string const & key);
989
template void DataManager::setData<AnalogTimeSeries>(std::string const & key, TimeKey const & time_key);
990
template void DataManager::setData<AnalogTimeSeries>(std::string const & key, std::shared_ptr<AnalogTimeSeries> data, TimeKey const & time_key);
991

992
template std::shared_ptr<DigitalEventSeries> DataManager::getData<DigitalEventSeries>(std::string const & key);
993
template void DataManager::setData<DigitalEventSeries>(std::string const & key, TimeKey const & time_key);
994
template void DataManager::setData<DigitalEventSeries>(std::string const & key, std::shared_ptr<DigitalEventSeries> data, TimeKey const & time_key);
995

996
template std::shared_ptr<DigitalIntervalSeries> DataManager::getData<DigitalIntervalSeries>(std::string const & key);
997
template void DataManager::setData<DigitalIntervalSeries>(std::string const & key, TimeKey const & time_key);
998
template void DataManager::setData<DigitalIntervalSeries>(std::string const & key, std::shared_ptr<DigitalIntervalSeries> data, TimeKey const & time_key);
999

1000
template std::shared_ptr<LineData> DataManager::getData<LineData>(std::string const & key);
1001
template void DataManager::setData<LineData>(std::string const & key, TimeKey const & time_key);
1002
template void DataManager::setData<LineData>(std::string const & key, std::shared_ptr<LineData> data, TimeKey const & time_key);
1003

1004
template std::shared_ptr<MaskData> DataManager::getData<MaskData>(std::string const & key);
1005
template void DataManager::setData<MaskData>(std::string const & key, TimeKey const & time_key);
1006
template void DataManager::setData<MaskData>(std::string const & key, std::shared_ptr<MaskData> data, TimeKey const & time_key);
1007

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

1010
template std::shared_ptr<PointData> DataManager::getData<PointData>(std::string const & key);
1011
template void DataManager::setData<PointData>(std::string const & key, TimeKey const & time_key);
1012
template void DataManager::setData<PointData>(std::string const & key, std::shared_ptr<PointData> data, TimeKey const & time_key);
1013

1014
template std::shared_ptr<TensorData> DataManager::getData<TensorData>(std::string const & key);
1015
template void DataManager::setData<TensorData>(std::string const & key, TimeKey const & time_key);
1016
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