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

paulmthompson / WhiskerToolbox / 14319867210

07 Apr 2025 09:29PM UTC coverage: 12.911% (-0.4%) from 13.308%
14319867210

push

github

paulmthompson
can load multi channel digital events

0 of 89 new or added lines in 6 files covered. (0.0%)

5 existing lines in 2 files now uncovered.

208 of 1611 relevant lines covered (12.91%)

1.26 hits per line

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

4.18
/src/WhiskerToolbox/DataManager/DataManager.cpp
1

2
#include "DataManager.hpp"
3
#include "AnalogTimeSeries/Analog_Time_Series.hpp"
4
#include "DigitalTimeSeries/Digital_Event_Series.hpp"
5
#include "DigitalTimeSeries/Digital_Interval_Series.hpp"
6
#include "Lines/Line_Data.hpp"
7
#include "Masks/Mask_Data.hpp"
8
#include "Media/Video_Data.hpp"
9
#include "Points/Point_Data.hpp"
10
#include "Tensors/Tensor_Data.hpp"
11

12
#include "AnalogTimeSeries/Analog_Time_Series_Loader.hpp"
13
#include "DigitalTimeSeries/Digital_Event_Series_Loader.hpp"
14
#include "DigitalTimeSeries/Digital_Interval_Series_Loader.hpp"
15
#include "Masks/Mask_Data_Loader.hpp"
16
#include "Media/Video_Data_Loader.hpp"
17
#include "Points/Point_Data_Loader.hpp"
18

19
#include "loaders/CSV_Loaders.hpp"
20
#include "loaders/binary_loaders.hpp"
21
#include "transforms/data_transforms.hpp"
22

23
#include "TimeFrame.hpp"
24

25
#include "utils/container.hpp"
26
#include "utils/glob.hpp"
27
#include "utils/hdf5_mask_load.hpp"
28

29
#include "nlohmann/json.hpp"
30
#include "utils/string_manip.hpp"
31

32
#include <filesystem>
33
#include <fstream>
34
#include <iostream>
35
#include <optional>
36
#include <regex>
37

38
using namespace nlohmann;
39

40
DataManager::DataManager() {
2✔
41
    _times["time"] = std::make_shared<TimeFrame>();
6✔
42
    _data["media"] = std::make_shared<MediaData>();
6✔
43

44
    setTimeFrame("media", "time");
10✔
45
    _output_path = std::filesystem::current_path();
2✔
46
}
2✔
47

48
void DataManager::setTimeFrame(std::string const & data_key, std::string const & time_key) {
2✔
49
    //Check that data_key is in _data
50
    if (_data.find(data_key) == _data.end()) {
2✔
51
        std::cerr << "Data key not found in DataManager: " << data_key << std::endl;
×
52
        return;
×
53
    }
54

55
    //Check that time_key is in _times
56
    if (_times.find(time_key) == _times.end()) {
2✔
57
        std::cerr << "Time key not found in DataManager: " << time_key << std::endl;
×
58
        return;
×
59
    }
60

61
    _time_frames[data_key] = time_key;
2✔
62
}
63

64
std::vector<std::vector<float>> read_ragged_hdf5(std::string const & filepath, std::string const & key) {
×
65
    auto myvector = load_ragged_array<float>(filepath, key);
×
66
    return myvector;
×
67
}
68

69
std::vector<int> read_array_hdf5(std::string const & filepath, std::string const & key) {
×
70
    auto myvector = load_array<int>(filepath, key);
×
71
    return myvector;
×
72
}
73

74
enum class DataType {
75
    Video,
76
    Points,
77
    Mask,
78
    Line,
79
    Analog,
80
    DigitalEvent,
81
    DigitalInterval,
82
    Tensor,
83
    Time,
84
    Unknown
85
};
86

87
DataType stringToDataType(std::string const & data_type_str) {
×
88
    if (data_type_str == "video") return DataType::Video;
×
89
    if (data_type_str == "points") return DataType::Points;
×
90
    if (data_type_str == "mask") return DataType::Mask;
×
91
    if (data_type_str == "line") return DataType::Line;
×
92
    if (data_type_str == "analog") return DataType::Analog;
×
93
    if (data_type_str == "digital_event") return DataType::DigitalEvent;
×
94
    if (data_type_str == "digital_interval") return DataType::DigitalInterval;
×
95
    if (data_type_str == "tensor") return DataType::Tensor;
×
96
    if (data_type_str == "time") return DataType::Time;
×
97
    return DataType::Unknown;
×
98
}
99

100
std::optional<std::string> processFilePath(
×
101
        std::string const & file_path,
102
        std::filesystem::path const & base_path) {
103
    std::filesystem::path full_path = file_path;
×
104

105
    // Check for wildcard character
106
    if (file_path.find('*') != std::string::npos) {
×
107
        // Convert wildcard pattern to regex
108
        std::string const pattern = std::regex_replace(full_path.string(), std::regex("\\*"), ".*");
×
109
        std::regex const regex_pattern(pattern);
×
110

111
        // Iterate through the directory to find matching files
112
        for (auto const & entry: std::filesystem::directory_iterator(base_path)) {
×
113
            std::cout << "Checking " << entry.path().string() << " with full path " << full_path << std::endl;
×
114
            if (std::regex_match(entry.path().string(), regex_pattern)) {
×
115
                std::cout << "Loading file " << entry.path().string() << std::endl;
×
116
                return entry.path().string();
×
117
            }
118
        }
×
119
        return std::nullopt;
×
120
    } else {
×
121
        // Check if the file path is relative
122
        if (!std::filesystem::path(file_path).is_absolute()) {
×
123
            full_path = base_path / file_path;
×
124
        }
125
        // Check for the presence of the file
126
        if (std::filesystem::exists(full_path)) {
×
127
            std::cout << "Loading file " << full_path.string() << std::endl;
×
128
            return full_path.string();
×
129
        } else {
130
            return std::nullopt;
×
131
        }
132
    }
133
}
×
134

135
bool checkRequiredFields(json const & item, std::vector<std::string> const & requiredFields) {
×
136
    for (auto const & field: requiredFields) {
×
137
        if (!item.contains(field)) {
×
138
            std::cerr << "Error: Missing required field \"" << field << "\" in JSON item." << std::endl;
×
139
            return false;
×
140
        }
141
    }
142
    return true;
×
143
}
144

145
int DataManager::addCallbackToData(std::string const & key, ObserverCallback callback) {
×
146

147
    int id = -1;
×
148

149
    if (_data.find(key) != _data.end()) {
×
150
        auto data = _data[key];
×
151

152
        id = std::visit([callback](auto & x) {
×
153
            return x.get()->addObserver(callback);
×
154
        },
155
                        data);
156
    }
×
157

158
    return id;
×
159
}
160

161
void DataManager::removeCallbackFromData(std::string const & key, int callback_id) {
×
162
    if (_data.find(key) != _data.end()) {
×
163
        auto data = _data[key];
×
164

165
        std::visit([callback_id](auto & x) {
×
166
            x.get()->removeObserver(callback_id);
×
167
        },
×
168
                   data);
169
    }
×
170
}
×
171

172
void checkOptionalFields(json const & item, std::vector<std::string> const & optionalFields) {
×
173
    for (auto const & field: optionalFields) {
×
174
        if (!item.contains(field)) {
×
175
            std::cout << "Warning: Optional field \"" << field << "\" is missing in JSON item." << std::endl;
×
176
        }
177
    }
178
}
×
179

180
std::vector<DataInfo> load_data_from_json_config(DataManager * dm, std::string const & json_filepath) {
×
181
    std::vector<DataInfo> data_info_list;
×
182
    // Open JSON file
183
    std::ifstream ifs(json_filepath);
×
184
    if (!ifs.is_open()) {
×
185
        std::cerr << "Failed to open JSON file: " << json_filepath << std::endl;
×
186
        return data_info_list;
×
187
    }
188

189
    // Parse JSON
190
    json j;
×
191
    ifs >> j;
×
192

193
    // get base path of filepath
194
    std::filesystem::path const base_path = std::filesystem::path(json_filepath).parent_path();
×
195

196
    // Iterate through JSON array
197
    for (auto const & item: j) {
×
198

199
        if (!checkRequiredFields(item, {"data_type", "name", "filepath"})) {
×
200
            continue;// Exit if any required field is missing
×
201
        }
202

203
        std::string const data_type_str = item["data_type"];
×
204
        DataType const data_type = stringToDataType(data_type_str);
×
205
        if (data_type == DataType::Unknown) {
×
206
            std::cout << "Unknown data type: " << data_type_str << std::endl;
×
207
            continue;
×
208
        }
209

210
        std::string const name = item["name"];
×
211

212
        auto file_exists = processFilePath(item["filepath"], base_path);
×
213
        if (!file_exists) {
×
214
            std::cerr << "File does not exist: " << item["filepath"] << std::endl;
×
215
            continue;
×
216
        }
217

218
        std::string const file_path = file_exists.value();
×
219

220
        switch (data_type) {
×
221
            case DataType::Video: {
×
222
                // Create VideoData object
223
                auto video_data = load_video_into_VideoData(file_path);
×
224

225
                // Add VideoData to DataManager
226
                dm->setMedia(video_data);
×
227

228
                data_info_list.push_back({name, "VideoData", ""});
×
229
                break;
×
230
            }
×
231
            case DataType::Points: {
×
232

233
                auto point_data = load_into_PointData(file_path, item);
×
234

235
                dm->setData<PointData>(name, point_data);
×
236

237
                std::string const color = item.value("color", "#0000FF");
×
238
                data_info_list.push_back({name, "PointData", color});
×
239
                break;
×
240
            }
×
241
            case DataType::Mask: {
×
242

243
                auto mask_data = load_into_MaskData(file_path, item);
×
244

245
                std::string const color = item.value("color", "0000FF");
×
246
                dm->setData<MaskData>(name, mask_data);
×
247

248
                data_info_list.push_back({name, "MaskData", color});
×
249

250
                if (item.contains("operations")) {
×
251

252
                    for (auto const & operation: item["operations"]) {
×
253

254
                        std::string const operation_type = operation["type"];
×
255

256
                        if (operation_type == "area") {
×
257
                            std::cout << "Calculating area for mask: " << name << std::endl;
×
258
                            auto area_data = area(dm->getData<MaskData>(name));
×
259
                            std::string const output_name = name + "_area";
×
260
                            dm->setData<AnalogTimeSeries>(output_name, area_data);
×
261
                        }
×
262
                    }
×
263
                }
264
                break;
×
265
            }
×
266
            case DataType::Line: {
×
267

268
                auto line_map = load_line_csv(file_path);
×
269

270
                //Get the whisker name from the filename using filesystem
271
                auto whisker_filename = std::filesystem::path(file_path).filename().string();
×
272

273
                //Remove .csv suffix from filename
274
                auto whisker_name = remove_extension(whisker_filename);
×
275

276
                dm->setData<LineData>(whisker_name, std::make_shared<LineData>(line_map));
×
277

278
                std::string const color = item.value("color", "0000FF");
×
279

280
                data_info_list.push_back({name, "LineData", color});
×
281

282
                break;
×
283
            }
×
284
            case DataType::Analog: {
×
285

286
                auto analog_time_series = load_into_AnalogTimeSeries(file_path, item);
×
287

288
                for (int channel = 0; channel < analog_time_series.size(); channel++) {
×
289
                    std::string const channel_name = name + "_" + std::to_string(channel);
×
290

291
                    dm->setData<AnalogTimeSeries>(channel_name, analog_time_series[channel]);
×
292

293
                    if (item.contains("clock")) {
×
294
                        std::string const clock = item["clock"];
×
295
                        dm->setTimeFrame(channel_name, clock);
×
296
                    }
×
297
                }
×
298
                break;
×
299
            }
×
300
            case DataType::DigitalEvent: {
×
301

302
                auto digital_event_series = load_into_DigitalEventSeries(file_path, item);
×
303

NEW
304
                for (int channel = 0; channel < digital_event_series.size(); channel++) {
×
NEW
305
                    std::string const channel_name = name + "_" + std::to_string(channel);
×
306

NEW
307
                    dm->setData<DigitalEventSeries>(channel_name, digital_event_series[channel]);
×
308

NEW
309
                    if (item.contains("clock")) {
×
NEW
310
                        std::string const clock = item["clock"];
×
NEW
311
                        dm->setTimeFrame(channel_name, clock);
×
NEW
312
                    }
×
NEW
313
                }
×
314
                break;
×
315
            }
×
316
            case DataType::DigitalInterval: {
×
317

318
                auto digital_interval_series = load_into_DigitalIntervalSeries(file_path, item);
×
319
                dm->setData<DigitalIntervalSeries>(name, digital_interval_series);
×
320

321
                break;
×
322
            }
×
323
            case DataType::Tensor: {
×
324

325
                if (item["format"] == "numpy") {
×
326

327
                    TensorData tensor_data;
×
328
                    loadNpyToTensorData(file_path, tensor_data);
×
329

330
                    dm->setData<TensorData>(name, std::make_shared<TensorData>(tensor_data));
×
331

332
                } else {
×
333
                    std::cout << "Format " << item["format"] << " not found for " << name << std::endl;
×
334
                }
335
                break;
×
336
            }
337
            case DataType::Time: {
×
338

339
                if (item["format"] == "uint16") {
×
340

341
                    int const channel = item["channel"];
×
342
                    std::string const transition = item["transition"];
×
343

NEW
344
                    int const header_size = item.value("header_size", 0);
×
345

NEW
346
                    auto opts = Loader::BinaryAnalogOptions{.file_path = file_path,
×
NEW
347
                                                            .header_size_bytes = static_cast<size_t>(header_size)};
×
UNCOV
348
                    auto data = readBinaryFile<uint16_t>(opts);
×
349

350
                    auto digital_data = Loader::extractDigitalData(data, channel);
×
351
                    auto events = Loader::extractEvents(digital_data, transition);
×
352

353
                    // convert to int with std::transform
354
                    std::vector<int> events_int;
×
355
                    events_int.reserve(events.size());
×
356
                    for (auto e: events) {
×
357
                        events_int.push_back(static_cast<int>(e));
×
358
                    }
359
                    std::cout << "Loaded " << events_int.size() << " events for " << name << std::endl;
×
360

361
                    auto timeframe = std::make_shared<TimeFrame>(events_int);
×
362
                    dm->setTime(name, timeframe);
×
363
                }
×
364

365
                if (item["format"] == "uint16_length") {
×
366

NEW
367
                    int const header_size = item.value("header_size", 0);
×
368

NEW
369
                    auto opts = Loader::BinaryAnalogOptions{.file_path = file_path,
×
NEW
370
                                                            .header_size_bytes = static_cast<size_t>(header_size)};
×
UNCOV
371
                    auto data = readBinaryFile<uint16_t>(opts);
×
372

373
                    std::vector<int> t(data.size());
×
374
                    std::iota(std::begin(t), std::end(t), 0);
×
375

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

378
                    auto timeframe = std::make_shared<TimeFrame>(t);
×
379
                    dm->setTime(name, timeframe);
×
380
                }
×
381
                break;
×
382
            }
383
            default:
×
384
                std::cout << "Unsupported data type: " << data_type_str << std::endl;
×
385
                continue;
×
386
        }
×
387
        if (item.contains("clock")) {
×
388
            std::string const clock = item["clock"];
×
389
            std::cout << "Setting time for " << name << " to " << clock << std::endl;
×
390
            dm->setTimeFrame(name, clock);
×
391
        }
×
392
    }
×
393

394
    return data_info_list;
395
}
×
396

397
std::string DataManager::getType(std::string const & key) const {
×
398
    auto it = _data.find(key);
×
399
    if (it != _data.end()) {
×
400
        if (std::holds_alternative<std::shared_ptr<MediaData>>(it->second)) {
×
401
            return "MediaData";
×
402
        } else if (std::holds_alternative<std::shared_ptr<PointData>>(it->second)) {
×
403
            return "PointData";
×
404
        } else if (std::holds_alternative<std::shared_ptr<LineData>>(it->second)) {
×
405
            return "LineData";
×
406
        } else if (std::holds_alternative<std::shared_ptr<MaskData>>(it->second)) {
×
407
            return "MaskData";
×
408
        } else if (std::holds_alternative<std::shared_ptr<AnalogTimeSeries>>(it->second)) {
×
409
            return "AnalogTimeSeries";
×
410
        } else if (std::holds_alternative<std::shared_ptr<DigitalEventSeries>>(it->second)) {
×
411
            return "DigitalEventSeries";
×
412
        } else if (std::holds_alternative<std::shared_ptr<DigitalIntervalSeries>>(it->second)) {
×
413
            return "DigitalIntervalSeries";
×
414
        } else if (std::holds_alternative<std::shared_ptr<TensorData>>(it->second)) {
×
415
            return "TensorData";
×
416
        }
417
        return "Unknown";
×
418
    }
419
}
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