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

paulmthompson / WhiskerToolbox / 15939366926

28 Jun 2025 01:59AM UTC coverage: 71.74% (+0.1%) from 71.62%
15939366926

push

github

paulmthompson
convert mask data over to strong typed time

208 of 218 new or added lines in 16 files covered. (95.41%)

42 existing lines in 3 files now uncovered.

11340 of 15807 relevant lines covered (71.74%)

1133.35 hits per line

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

0.0
/src/WhiskerToolbox/DataManager/Masks/IO/Image/Mask_Data_Image.cpp
1
#include "Mask_Data_Image.hpp"
2

3
#include "Masks/Mask_Data.hpp"
4
#include "Masks/masks.hpp"
5
#include "Points/points.hpp"
6
#include "utils/string_manip.hpp"
7

8
#include <opencv2/imgcodecs.hpp>
9
#include <opencv2/opencv.hpp>
10

11
#include <algorithm>
12
#include <filesystem>
13
#include <iostream>
14
#include <regex>
15
#include <vector>
16

17
std::shared_ptr<MaskData> load(ImageMaskLoaderOptions const & opts) {
×
18
    auto mask_data = std::make_shared<MaskData>();
×
19

20
    // Validate directory
21
    if (!std::filesystem::exists(opts.directory_path) || !std::filesystem::is_directory(opts.directory_path)) {
×
22
        std::cerr << "Error: Directory does not exist: " << opts.directory_path << std::endl;
×
23
        return mask_data;
×
24
    }
25

26
    // Get list of image files matching the pattern
27
    std::vector<std::filesystem::path> image_files;
×
28
    std::string pattern = opts.file_pattern;
×
29

30
    // Convert wildcard pattern to regex
31
    std::string regex_pattern = std::regex_replace(pattern, std::regex("\\*"), ".*");
×
32
    std::regex file_regex(regex_pattern, std::regex_constants::icase);
×
33

34
    for (auto const & entry: std::filesystem::directory_iterator(opts.directory_path)) {
×
35
        if (entry.is_regular_file()) {
×
36
            std::string filename = entry.path().filename().string();
×
37
            if (std::regex_match(filename, file_regex)) {
×
38
                image_files.push_back(entry.path());
×
39
            }
40
        }
×
41
    }
×
42

43
    if (image_files.empty()) {
×
44
        std::cerr << "Warning: No image files found matching pattern '" << opts.file_pattern
×
45
                  << "' in directory: " << opts.directory_path << std::endl;
×
46
        return mask_data;
×
47
    }
48

49
    // Sort files to ensure consistent ordering
50
    std::sort(image_files.begin(), image_files.end());
×
51

52
    int files_loaded = 0;
×
53
    int files_skipped = 0;
×
54

55
    std::cout << "Loading mask images from directory: " << opts.directory_path << std::endl;
×
56
    std::cout << "Found " << image_files.size() << " image files matching pattern: " << opts.file_pattern << std::endl;
×
57

58
    for (auto const & file_path: image_files) {
×
59
        std::string filename = file_path.filename().string();
×
60
        std::string stem = file_path.stem().string();// Remove extension
×
61

62
        // Remove prefix if specified
63
        if (!opts.filename_prefix.empty()) {
×
64
            if (stem.find(opts.filename_prefix) != 0) {
×
65
                std::cerr << "Warning: File '" << filename
66
                          << "' does not start with expected prefix '" << opts.filename_prefix << "'" << std::endl;
×
67
                files_skipped++;
×
68
                continue;
×
69
            }
70
            stem = stem.substr(opts.filename_prefix.length());// Remove prefix
×
71
        }
72

73
        // Parse frame number
74
        int frame_number;
75
        try {
76
            frame_number = std::stoi(stem);
×
77
        } catch (std::exception const & e) {
×
78
            std::cerr << "Warning: Could not parse frame number from filename: " << filename << std::endl;
×
79
            files_skipped++;
×
80
            continue;
×
81
        }
×
82

83
        // Load the image using OpenCV
84
        cv::Mat image = cv::imread(file_path.string(), cv::IMREAD_GRAYSCALE);
×
85
        if (image.empty()) {
×
86
            std::cerr << "Warning: Could not load image: " << file_path.string() << std::endl;
×
87
            files_skipped++;
×
88
            continue;
×
89
        }
90

91
        // Extract mask points from image
92
        std::vector<Point2D<uint32_t>> mask_points;
×
93
        int const width = image.cols;
×
94
        int const height = image.rows;
×
95

96
        for (int y = 0; y < height; ++y) {
×
97
            for (int x = 0; x < width; ++x) {
×
98
                // Get pixel intensity (0-255)
99
                uint8_t pixel_value = image.at<uint8_t>(y, x);
×
100

101
                // Apply threshold and inversion logic
102
                bool is_mask_pixel;
103
                if (opts.invert_mask) {
×
104
                    is_mask_pixel = pixel_value < opts.threshold_value;
×
105
                } else {
106
                    is_mask_pixel = pixel_value >= opts.threshold_value;
×
107
                }
108

109
                if (is_mask_pixel) {
×
110
                    //mask_points.emplace_back(static_cast<float>(x), static_cast<float>(y)); // This fails on mac
111
                    mask_points.push_back(Point2D<uint32_t>{static_cast<uint32_t>(x), static_cast<uint32_t>(y)});
×
112
                }
113
            }
114
        }
115

116
        // Add mask to data if we have points
117
        if (!mask_points.empty()) {
×
NEW
118
            mask_data->addAtTime(TimeFrameIndex(static_cast<size_t>(frame_number)), std::move(mask_points), false);
×
119
            files_loaded++;
×
120
        } else {
121
            std::cout << "Warning: No mask pixels found in image: " << filename << std::endl;
×
122
            files_skipped++;
×
123
        }
124
    }
×
125

126
    // Notify observers once at the end
127
    if (files_loaded > 0) {
×
128
        mask_data->notifyObservers();
×
129
    }
130

131
    std::cout << "Image mask loading complete: " << files_loaded << " files loaded";
×
132
    if (files_skipped > 0) {
×
133
        std::cout << ", " << files_skipped << " files skipped";
×
134
    }
135
    std::cout << std::endl;
×
136

137
    return mask_data;
×
138
}
×
139

140
void save(MaskData const * mask_data, ImageMaskSaverOptions const & opts) {
×
141
    if (!mask_data) {
×
142
        std::cerr << "Error: MaskData pointer is null" << std::endl;
×
143
        return;
×
144
    }
145

146
    // Create output directory if it doesn't exist
147
    if (!std::filesystem::exists(opts.parent_dir)) {
×
148
        std::filesystem::create_directories(opts.parent_dir);
×
149
        std::cout << "Created directory: " << opts.parent_dir << std::endl;
×
150
    }
151

152
    int files_saved = 0;
×
153
    int files_skipped = 0;
×
154

155
    auto mask_data_size = mask_data->getImageSize();
×
156

157
    std::cout << "Saving mask images to directory: " << opts.parent_dir << std::endl;
×
158

159
    // Iterate through all masks with their timestamps
160
    for (auto const & time_mask_pair: mask_data->getAllAsRange()) {
×
NEW
161
        auto time = time_mask_pair.time;
×
162
        std::vector<Mask2D> const & masks = time_mask_pair.masks;
×
163

164
        if (masks.empty()) {
×
165
            files_skipped++;
×
166
            continue;
×
167
        }
168

169
        // Create output image with desired dimensions
170
        cv::Mat output_img = cv::Mat::zeros(opts.image_height, opts.image_width, CV_8UC1);
×
171
        output_img.setTo(opts.background_value);
×
172

173
        // Resize each mask and draw it on the output image
174
        ImageSize dest_size{opts.image_width, opts.image_height};
×
175
        for (Mask2D const & mask: masks) {
×
176
            // Resize the mask using our new resize function
177
            Mask2D resized_mask = resize_mask(mask, mask_data_size, dest_size);
×
178

179
            // Draw the resized mask on the output image
180
            for (Point2D<uint32_t> const & point: resized_mask) {
×
181
                int x = static_cast<int>(point.x);
×
182
                int y = static_cast<int>(point.y);
×
183

184
                // Check bounds (should already be valid after resize, but be safe)
185
                if (x >= 0 && x < opts.image_width && y >= 0 && y < opts.image_height) {
×
186
                    output_img.at<uint8_t>(y, x) = static_cast<uint8_t>(opts.mask_value);
×
187
                }
188
            }
189
        }
×
190

191
        // Generate filename using the pad_frame_id utility
192
        std::string filename;
×
193
        if (!opts.filename_prefix.empty()) {
×
194
            filename = opts.filename_prefix;
×
195
        }
196

197
        // Add zero-padded frame number
NEW
198
        filename += pad_frame_id(time.getValue(), opts.frame_number_padding);
×
199

200
        // Add extension based on format
201
        std::string extension = "." + opts.image_format;
×
202
        std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
×
203
        filename += extension;
×
204

205
        // Full path
206
        std::filesystem::path full_path = std::filesystem::path(opts.parent_dir) / filename;
×
207

208
        // Check if file exists and handle according to overwrite setting
209
        if (std::filesystem::exists(full_path) && !opts.overwrite_existing) {
×
210
            std::cout << "Skipping existing file: " << full_path.string() << std::endl;
×
211
            files_skipped++;
×
212
            continue;
×
213
        }
214

215
        // Save the image using OpenCV
216
        if (cv::imwrite(full_path.string(), output_img)) {
×
217
            files_saved++;
×
218
            if (std::filesystem::exists(full_path) && opts.overwrite_existing) {
×
219
                std::cout << "Overwritten existing file: " << full_path.string() << std::endl;
×
220
            }
221
        } else {
222
            std::cerr << "Warning: Failed to save image: " << full_path.string() << std::endl;
×
223
            files_skipped++;
×
224
        }
225
    }
×
226

227
    std::cout << "Image mask saving complete: " << files_saved << " files saved";
×
228
    if (files_skipped > 0) {
×
229
        std::cout << ", " << files_skipped << " files skipped";
×
230
    }
231
    std::cout << std::endl;
×
232
}
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