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

paulmthompson / WhiskerToolbox / 15430655463

04 Jun 2025 12:26AM UTC coverage: 40.974% (+17.1%) from 23.832%
15430655463

push

github

paulmthompson
fix self copy with mask data

5 of 5 new or added lines in 2 files covered. (100.0%)

945 existing lines in 24 files now uncovered.

3003 of 7329 relevant lines covered (40.97%)

731.35 hits per line

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

0.0
/src/WhiskerToolbox/DataManager/utils/opencv_utility.cpp
1
#include "opencv_utility.hpp"
2

3
#include <opencv2/imgcodecs.hpp>
4
#include <opencv2/imgproc.hpp>
5
#include <opencv2/opencv.hpp>
6
#include <opencv2/photo.hpp>
7
#include <iostream>
8

9
cv::Mat load_mask_from_image(std::string const & filename, bool const invert) {
×
10
    cv::Mat image = cv::imread(filename, cv::IMREAD_GRAYSCALE);
×
11

12
    if (image.empty()) {
×
UNCOV
13
        std::cerr << "Could not open or find the image: " << filename << std::endl;
×
14
        return cv::Mat(); // Return an empty matrix
×
15
    }
16

17
    if (invert) {
×
UNCOV
18
        cv::bitwise_not(image, image);
×
19
    }
20

21
    return image;
×
UNCOV
22
}
×
23

UNCOV
24
cv::Mat convert_vector_to_mat(std::vector<uint8_t> & vec, ImageSize const image_size) {
×
25
    // Determine the number of channels
UNCOV
26
    int channels = static_cast<int>(vec.size()) / (image_size.width * image_size.height);
×
27

28
    // Determine the OpenCV type based on the number of channels
29
    int cv_type;
30
    if (channels == 1) {
×
UNCOV
31
        cv_type = CV_8UC1; // Grayscale
×
32
    } else if (channels == 3) {
×
33
        cv_type = CV_8UC3; // BGR
×
UNCOV
34
    } else if (channels == 4) {
×
35
        cv_type = CV_8UC4; // BGRA
×
36
    } else {
37
        std::cerr << "Unsupported number of channels: " << channels << std::endl;
×
38
        return cv::Mat(); // Return an empty matrix
×
39
    }
40

UNCOV
41
    return cv::Mat(image_size.height, image_size.width, cv_type, vec.data());
×
42
}
43

UNCOV
44
cv::Mat convert_vector_to_mat(std::vector<Point2D<float>> & vec, ImageSize const image_size) {
×
45
    cv::Mat mask_image(image_size.height, image_size.width, CV_8UC1, cv::Scalar(0));
×
46

47
    for (auto const & point : vec) {
×
UNCOV
48
        int x = static_cast<int>(std::round(point.x));
×
49
        int y = static_cast<int>(std::round(point.y));
×
50

51
        // Check bounds
UNCOV
52
        if (x >= 0 && x < image_size.width && y >= 0 && y < image_size.height) {
×
53
            mask_image.at<uint8_t>(y, x) = 255; // Set pixel to white (255)
×
54
        }
55
    }
56

UNCOV
57
    return mask_image;
×
58
}
59

UNCOV
60
void convert_mat_to_vector(std::vector<uint8_t> & vec, cv::Mat & mat, ImageSize const image_size) {
×
61
    // Check if the matrix has the expected size
UNCOV
62
    if (mat.rows != image_size.height || mat.cols != image_size.width) {
×
63
        std::cerr << "Matrix size does not match the expected image size." << std::endl;
×
64
        return;
×
65
    }
66

67
    // Resize the vector to hold all the pixel data
UNCOV
68
    vec.resize(mat.rows * mat.cols * mat.channels());
×
69

70
    // Copy data from the matrix to the vector
UNCOV
71
    std::memcpy(vec.data(), mat.data, vec.size());
×
72
}
73

UNCOV
74
std::vector<Point2D<float>> create_mask(cv::Mat const & mat) {
×
UNCOV
75
    std::vector<Point2D<float>> mask;
×
76

UNCOV
77
    for (int y = 0; y < mat.rows; ++y) {
×
78
        for (int x = 0; x < mat.cols; ++x) {
×
79
            if (mat.at<uint8_t>(y, x) > 0) { // Assuming a binary mask where 0 is background
×
80
                mask.push_back({static_cast<float>(x), static_cast<float>(y)});
×
81
            }
82
        }
83
    }
84

85
    return mask;
×
UNCOV
86
}
×
87

88
void grow_mask(cv::Mat & mat, int const dilation_size) {
×
UNCOV
89
    cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE,
×
UNCOV
90
                                                cv::Size(2 * dilation_size + 1, 2 * dilation_size + 1),
×
UNCOV
91
                                                cv::Point(dilation_size, dilation_size));
×
UNCOV
92
    cv::dilate(mat, mat, element);
×
93
}
×
94

UNCOV
95
void median_blur(cv::Mat & mat, int const kernel_size) {
×
96
    cv::medianBlur(mat, mat, kernel_size);
×
97
}
×
98

99
// New options-based implementations
100
void linear_transform(cv::Mat & mat, ContrastOptions const& options) {
×
101
    mat.convertTo(mat, -1, options.alpha, options.beta);
×
102
}
×
103

104
void gamma_transform(cv::Mat & mat, GammaOptions const& options) {
×
105
    cv::Mat lookupTable(1, 256, CV_8U);
×
106
    uchar* p = lookupTable.ptr();
×
UNCOV
107
    for (int i = 0; i < 256; ++i) {
×
108
        p[i] = cv::saturate_cast<uchar>(pow(i / 255.0, options.gamma) * 255.0);
×
109
    }
110
    cv::LUT(mat, lookupTable, mat);
×
UNCOV
111
}
×
112

113
void clahe(cv::Mat & mat, ClaheOptions const& options) {
×
UNCOV
114
    cv::Ptr<cv::CLAHE> clahe_ptr = cv::createCLAHE(options.clip_limit, cv::Size(options.grid_size, options.grid_size));
×
UNCOV
115
    clahe_ptr->apply(mat, mat);
×
116
}
×
117

118
void sharpen_image(cv::Mat & mat, SharpenOptions const& options) {
×
119
    cv::Mat blurred;
×
UNCOV
120
    cv::GaussianBlur(mat, blurred, cv::Size(0, 0), options.sigma);
×
UNCOV
121
    cv::addWeighted(mat, 1.0 + 1.0, blurred, -1.0, 0, mat);
×
UNCOV
122
}
×
123

124
void bilateral_filter(cv::Mat & mat, BilateralOptions const& options) {
×
UNCOV
125
    cv::Mat temp;
×
126
    cv::bilateralFilter(mat, temp, options.diameter, options.sigma_color, options.sigma_spatial);
×
127
    mat = temp;
×
128
}
×
129

130
void median_filter(cv::Mat & mat, MedianOptions const& options) {
×
131
    if (options.kernel_size >= 3 && options.kernel_size % 2 == 1) {
×
UNCOV
132
        cv::medianBlur(mat, mat, options.kernel_size);
×
133
    }
134
}
×
135

UNCOV
136
std::vector<Point2D<float>> dilate_mask(std::vector<Point2D<float>> const& mask, ImageSize image_size, MaskDilationOptions const& options) {
×
137
    if (mask.empty() || !options.active) {
×
UNCOV
138
        return mask;
×
139
    }
140
    
141
    // Convert point-based mask to cv::Mat
UNCOV
142
    cv::Mat mask_mat = cv::Mat::zeros(image_size.height, image_size.width, CV_8UC1);
×
143
    
144
    // Fill the mask matrix with points
UNCOV
145
    for (auto const& point : mask) {
×
146
        int x = static_cast<int>(std::round(point.x));
×
147
        int y = static_cast<int>(std::round(point.y));
×
UNCOV
148
        if (x >= 0 && x < image_size.width && y >= 0 && y < image_size.height) {
×
UNCOV
149
            mask_mat.at<uint8_t>(y, x) = 255;
×
150
        }
151
    }
152
    
153
    // Apply dilation/erosion
154
    dilate_mask_mat(mask_mat, options);
×
155
    
156
    // Convert back to point-based representation
UNCOV
157
    return create_mask(mask_mat);
×
UNCOV
158
}
×
159

UNCOV
160
void dilate_mask_mat(cv::Mat& mat, MaskDilationOptions const& options) {
×
UNCOV
161
    if (!options.active) {
×
UNCOV
162
        return;
×
163
    }
164
    
UNCOV
165
    int kernel_size = options.is_grow_mode ? options.grow_size : options.shrink_size;
×
166
    
UNCOV
167
    if (kernel_size <= 0) {
×
UNCOV
168
        return;
×
169
    }
170
    
UNCOV
171
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(kernel_size, kernel_size));
×
172
    
UNCOV
173
    if (options.is_grow_mode) {
×
UNCOV
174
        cv::dilate(mat, mat, kernel);
×
175
    } else {
UNCOV
176
        cv::erode(mat, mat, kernel);
×
177
    }
UNCOV
178
}
×
179

UNCOV
180
std::vector<uint8_t> apply_magic_eraser_with_options(std::vector<uint8_t> const& image, 
×
181
                                                    ImageSize image_size, 
182
                                                    std::vector<uint8_t> const& mask,
183
                                                    MagicEraserOptions const& options) {
184
    // Create mutable copies for the conversion functions
UNCOV
185
    std::vector<uint8_t> image_copy = image;
×
UNCOV
186
    std::vector<uint8_t> mask_copy = mask;
×
187
    
188
    // Convert the input vector to a cv::Mat
UNCOV
189
    cv::Mat inputImage = convert_vector_to_mat(image_copy, image_size);
×
UNCOV
190
    cv::Mat inputImage3Channel;
×
UNCOV
191
    cv::cvtColor(inputImage, inputImage3Channel, cv::COLOR_GRAY2BGR);
×
192

193
    // Apply median blur filter with configurable size
UNCOV
194
    cv::Mat medianBlurredImage;
×
UNCOV
195
    int filter_size = options.median_filter_size;
×
196
    // Ensure filter size is odd and within valid range
UNCOV
197
    if (filter_size % 2 == 0) filter_size += 1;
×
UNCOV
198
    if (filter_size < 3) filter_size = 3;
×
UNCOV
199
    if (filter_size > 101) filter_size = 101;
×
200
    
UNCOV
201
    cv::medianBlur(inputImage, medianBlurredImage, filter_size);
×
UNCOV
202
    cv::Mat medianBlurredImage3Channel;
×
UNCOV
203
    cv::cvtColor(medianBlurredImage, medianBlurredImage3Channel, cv::COLOR_GRAY2BGR);
×
204

UNCOV
205
    cv::Mat maskImage = convert_vector_to_mat(mask_copy, image_size);
×
206

UNCOV
207
    cv::Mat smoothedMask;
×
UNCOV
208
    cv::GaussianBlur(maskImage, smoothedMask, cv::Size(15, 15), 0);
×
209

UNCOV
210
    cv::threshold(smoothedMask, smoothedMask, 1, 255, cv::THRESH_BINARY);
×
211

UNCOV
212
    for (int y = 0; y < smoothedMask.rows; ++y) {
×
UNCOV
213
        for (int x = 0; x < smoothedMask.cols; ++x) {
×
UNCOV
214
            if (smoothedMask.at<uint8_t>(y, x) == 0) {
×
UNCOV
215
                smoothedMask.at<uint8_t>(y, x) = 1;
×
216
            }
217
        }
218
    }
219

UNCOV
220
    cv::Mat mask3Channel;
×
UNCOV
221
    cv::cvtColor(smoothedMask, mask3Channel, cv::COLOR_GRAY2BGR);
×
222

UNCOV
223
    cv::Mat outputImage;
×
UNCOV
224
    cv::Point const center(image_size.width / 2, image_size.height / 2);
×
225

UNCOV
226
    cv::seamlessClone(medianBlurredImage3Channel, inputImage3Channel, mask3Channel, center, outputImage, cv::NORMAL_CLONE);
×
227

UNCOV
228
    cv::Mat outputImageGray;
×
UNCOV
229
    cv::cvtColor(outputImage, outputImageGray, cv::COLOR_BGR2GRAY);
×
UNCOV
230
    auto output = std::vector<uint8_t>(static_cast<size_t>(image_size.height * image_size.width));
×
231

UNCOV
232
    convert_mat_to_vector(output, outputImageGray, image_size);
×
233

UNCOV
234
    return output;
×
UNCOV
235
}
×
236

UNCOV
237
void apply_magic_eraser(cv::Mat& mat, MagicEraserOptions const& options) {
×
238

UNCOV
239
    auto mask = options.mask;
×
240
    // Only apply if we have a valid mask
UNCOV
241
    if (mask.empty() || options.image_size.width <= 0 || options.image_size.height <= 0) {
×
UNCOV
242
        return;
×
243
    }
244
    
245
    // Check if the image dimensions match the stored mask dimensions
UNCOV
246
    if (mat.rows != options.image_size.height || mat.cols != options.image_size.width) {
×
UNCOV
247
        std::cerr << "Magic eraser: Image dimensions don't match stored mask dimensions" << std::endl;
×
UNCOV
248
        return;
×
249
    }
250
    
251
    // Convert the image to 3-channel for seamless cloning
UNCOV
252
    cv::Mat inputImage3Channel;
×
UNCOV
253
    if (mat.channels() == 1) {
×
UNCOV
254
        cv::cvtColor(mat, inputImage3Channel, cv::COLOR_GRAY2BGR);
×
255
    } else {
UNCOV
256
        inputImage3Channel = mat.clone();
×
257
    }
258

259
    // Apply median blur filter with configurable size
UNCOV
260
    cv::Mat medianBlurredImage;
×
UNCOV
261
    int filter_size = options.median_filter_size;
×
262
    // Ensure filter size is odd and within valid range
UNCOV
263
    if (filter_size % 2 == 0) filter_size += 1;
×
UNCOV
264
    if (filter_size < 3) filter_size = 3;
×
UNCOV
265
    if (filter_size > 101) filter_size = 101;
×
266
    
UNCOV
267
    cv::Mat grayMat;
×
UNCOV
268
    if (mat.channels() == 3) {
×
UNCOV
269
        cv::cvtColor(mat, grayMat, cv::COLOR_BGR2GRAY);
×
270
    } else {
UNCOV
271
        grayMat = mat.clone();
×
272
    }
273
    
UNCOV
274
    cv::medianBlur(grayMat, medianBlurredImage, filter_size);
×
UNCOV
275
    cv::Mat medianBlurredImage3Channel;
×
UNCOV
276
    cv::cvtColor(medianBlurredImage, medianBlurredImage3Channel, cv::COLOR_GRAY2BGR);
×
277

278
    // Create mask image from stored mask data
UNCOV
279
    cv::Mat maskImage = convert_vector_to_mat(mask, options.image_size);
×
280

UNCOV
281
    cv::Mat smoothedMask;
×
UNCOV
282
    cv::GaussianBlur(maskImage, smoothedMask, cv::Size(15, 15), 0);
×
283

UNCOV
284
    cv::threshold(smoothedMask, smoothedMask, 1, 255, cv::THRESH_BINARY);
×
285

286
    // Ensure mask has valid values for seamless cloning
UNCOV
287
    for (int y = 0; y < smoothedMask.rows; ++y) {
×
UNCOV
288
        for (int x = 0; x < smoothedMask.cols; ++x) {
×
UNCOV
289
            if (smoothedMask.at<uint8_t>(y, x) == 0) {
×
UNCOV
290
                smoothedMask.at<uint8_t>(y, x) = 1;
×
291
            }
292
        }
293
    }
294

UNCOV
295
    cv::Mat mask3Channel;
×
UNCOV
296
    cv::cvtColor(smoothedMask, mask3Channel, cv::COLOR_GRAY2BGR);
×
297

UNCOV
298
    cv::Mat outputImage;
×
UNCOV
299
    cv::Point const center(options.image_size.width / 2, options.image_size.height / 2);
×
300

UNCOV
301
    cv::seamlessClone(medianBlurredImage3Channel, inputImage3Channel, mask3Channel, center, outputImage, cv::NORMAL_CLONE);
×
302

303
    // Convert back to the original format
UNCOV
304
    if (mat.channels() == 1) {
×
UNCOV
305
        cv::cvtColor(outputImage, mat, cv::COLOR_BGR2GRAY);
×
306
    } else {
UNCOV
307
        mat = outputImage;
×
308
    }
UNCOV
309
}
×
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