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

paulmthompson / WhiskerToolbox / 17388125893

01 Sep 2025 09:47PM UTC coverage: 71.68% (-0.01%) from 71.692%
17388125893

push

github

paulmthompson
can set levels in contrast adjustment

0 of 8 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

31745 of 44287 relevant lines covered (71.68%)

1390.48 hits per line

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

0.0
/src/ImageProcessing/src/OpenCVUtility.cpp
1
#include "ImageProcessing/OpenCVUtility.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
namespace ImageProcessing {
10

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

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

19
    if (invert) {
×
20
        cv::bitwise_not(image, image);
×
21
    }
22

23
    return image;
×
24
}
×
25

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

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

43
    return cv::Mat(image_size.height, image_size.width, cv_type, vec.data());
×
44
}
45

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

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

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

59
    return mask_image;
×
60
}
61

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

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

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

76
std::vector<Point2D<uint32_t>> create_mask(cv::Mat const & mat) {
×
77
    std::vector<Point2D<uint32_t>> mask;
×
78

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

87
    return mask;
×
88
}
×
89

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

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

101
// New options-based implementations
102
void linear_transform(cv::Mat & mat, ContrastOptions const& options) {
×
NEW
103
    double alpha = options.alpha;
×
NEW
104
    int beta = options.beta;
×
105

106
    // Always calculate alpha and beta from min/max values for consistent behavior
NEW
107
    if (options.display_max <= options.display_min) {
×
NEW
108
        alpha = 1.0;
×
NEW
109
        beta = 0;
×
110
    } else {
NEW
111
        alpha = 255.0 / (options.display_max - options.display_min);
×
NEW
112
        beta = static_cast<int>(-alpha * options.display_min);
×
113
    }
114

NEW
115
    mat.convertTo(mat, -1, alpha, beta);
×
UNCOV
116
}
×
117

118
void gamma_transform(cv::Mat & mat, GammaOptions const& options) {
×
119
    cv::Mat lookupTable(1, 256, CV_8U);
×
120
    uchar* p = lookupTable.ptr();
×
121
    for (int i = 0; i < 256; ++i) {
×
122
        p[i] = cv::saturate_cast<uchar>(pow(i / 255.0, options.gamma) * 255.0);
×
123
    }
124
    cv::LUT(mat, lookupTable, mat);
×
125
}
×
126

127
void clahe(cv::Mat & mat, ClaheOptions const& options) {
×
128
    cv::Ptr<cv::CLAHE> clahe_ptr = cv::createCLAHE(options.clip_limit, cv::Size(options.grid_size, options.grid_size));
×
129
    clahe_ptr->apply(mat, mat);
×
130
}
×
131

132
void sharpen_image(cv::Mat & mat, SharpenOptions const& options) {
×
133
    cv::Mat blurred;
×
134
    cv::GaussianBlur(mat, blurred, cv::Size(0, 0), options.sigma);
×
135
    cv::addWeighted(mat, 1.0 + 1.0, blurred, -1.0, 0, mat);
×
136
}
×
137

138
void bilateral_filter(cv::Mat & mat, BilateralOptions const& options) {
×
139
    cv::Mat temp;
×
140
    cv::bilateralFilter(mat, temp, options.diameter, options.sigma_color, options.sigma_spatial);
×
141
    mat = temp;
×
142
}
×
143

144
void median_filter(cv::Mat & mat, MedianOptions const& options) {
×
145
    if (options.kernel_size >= 3 && options.kernel_size % 2 == 1) {
×
146
        cv::medianBlur(mat, mat, options.kernel_size);
×
147
    }
148
}
×
149

150
std::vector<Point2D<uint32_t>> dilate_mask(std::vector<Point2D<uint32_t>> const& mask, ImageSize image_size, MaskDilationOptions const& options) {
×
151
    if (mask.empty() || !options.active) {
×
152
        return mask;
×
153
    }
154
    
155
    // Convert point-based mask to cv::Mat
156
    cv::Mat mask_mat = cv::Mat::zeros(image_size.height, image_size.width, CV_8UC1);
×
157
    
158
    // Fill the mask matrix with points
159
    for (auto const& point : mask) {
×
160
        int x = static_cast<int>(std::round(point.x));
×
161
        int y = static_cast<int>(std::round(point.y));
×
162
        if (x >= 0 && x < image_size.width && y >= 0 && y < image_size.height) {
×
163
            mask_mat.at<uint8_t>(y, x) = 255;
×
164
        }
165
    }
166
    
167
    // Apply dilation/erosion
168
    dilate_mask_mat(mask_mat, options);
×
169
    
170
    // Convert back to point-based representation
171
    return create_mask(mask_mat);
×
172
}
×
173

174
void dilate_mask_mat(cv::Mat& mat, MaskDilationOptions const& options) {
×
175
    if (!options.active) {
×
176
        return;
×
177
    }
178
    
179
    int kernel_size = options.is_grow_mode ? options.grow_size : options.shrink_size;
×
180
    
181
    if (kernel_size <= 0) {
×
182
        return;
×
183
    }
184
    
185
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(kernel_size, kernel_size));
×
186
    
187
    if (options.is_grow_mode) {
×
188
        cv::dilate(mat, mat, kernel);
×
189
    } else {
190
        cv::erode(mat, mat, kernel);
×
191
    }
192
}
×
193

194
std::vector<uint8_t> apply_magic_eraser_with_options(std::vector<uint8_t> const& image, 
×
195
                                                    ImageSize image_size, 
196
                                                    std::vector<uint8_t> const& mask,
197
                                                    MagicEraserOptions const& options) {
198
    // Create mutable copies for the conversion functions
199
    std::vector<uint8_t> image_copy = image;
×
200
    std::vector<uint8_t> mask_copy = mask;
×
201
    
202
    // Convert the input vector to a cv::Mat
203
    cv::Mat inputImage = convert_vector_to_mat(image_copy, image_size);
×
204
    cv::Mat inputImage3Channel;
×
205
    cv::cvtColor(inputImage, inputImage3Channel, cv::COLOR_GRAY2BGR);
×
206

207
    // Apply median blur filter with configurable size
208
    cv::Mat medianBlurredImage;
×
209
    int filter_size = options.median_filter_size;
×
210
    // Ensure filter size is odd and within valid range
211
    if (filter_size % 2 == 0) filter_size += 1;
×
212
    if (filter_size < 3) filter_size = 3;
×
213
    if (filter_size > 101) filter_size = 101;
×
214
    
215
    cv::medianBlur(inputImage, medianBlurredImage, filter_size);
×
216
    cv::Mat medianBlurredImage3Channel;
×
217
    cv::cvtColor(medianBlurredImage, medianBlurredImage3Channel, cv::COLOR_GRAY2BGR);
×
218

219
    cv::Mat maskImage = convert_vector_to_mat(mask_copy, image_size);
×
220

221
    cv::Mat smoothedMask;
×
222
    cv::GaussianBlur(maskImage, smoothedMask, cv::Size(15, 15), 0);
×
223

224
    cv::threshold(smoothedMask, smoothedMask, 1, 255, cv::THRESH_BINARY);
×
225

226
    for (int y = 0; y < smoothedMask.rows; ++y) {
×
227
        for (int x = 0; x < smoothedMask.cols; ++x) {
×
228
            if (smoothedMask.at<uint8_t>(y, x) == 0) {
×
229
                smoothedMask.at<uint8_t>(y, x) = 1;
×
230
            }
231
        }
232
    }
233

234
    cv::Mat mask3Channel;
×
235
    cv::cvtColor(smoothedMask, mask3Channel, cv::COLOR_GRAY2BGR);
×
236

237
    cv::Mat outputImage;
×
238
    cv::Point const center(image_size.width / 2, image_size.height / 2);
×
239

240
    cv::seamlessClone(medianBlurredImage3Channel, inputImage3Channel, mask3Channel, center, outputImage, cv::NORMAL_CLONE);
×
241

242
    cv::Mat outputImageGray;
×
243
    cv::cvtColor(outputImage, outputImageGray, cv::COLOR_BGR2GRAY);
×
244
    auto output = std::vector<uint8_t>(static_cast<size_t>(image_size.height * image_size.width));
×
245

246
    convert_mat_to_vector(output, outputImageGray, image_size);
×
247

248
    return output;
×
249
}
×
250

251
void apply_magic_eraser(cv::Mat& mat, MagicEraserOptions const& options) {
×
252

253
    auto mask = options.mask;
×
254
    // Only apply if we have a valid mask
255
    if (mask.empty() || options.image_size.width <= 0 || options.image_size.height <= 0) {
×
256
        return;
×
257
    }
258
    
259
    // Check if the image dimensions match the stored mask dimensions
260
    if (mat.rows != options.image_size.height || mat.cols != options.image_size.width) {
×
261
        std::cerr << "Magic eraser: Image dimensions don't match stored mask dimensions" << std::endl;
×
262
        return;
×
263
    }
264
    
265
    // Convert the image to 3-channel for seamless cloning
266
    cv::Mat inputImage3Channel;
×
267
    if (mat.channels() == 1) {
×
268
        cv::cvtColor(mat, inputImage3Channel, cv::COLOR_GRAY2BGR);
×
269
    } else {
270
        inputImage3Channel = mat.clone();
×
271
    }
272

273
    // Apply median blur filter with configurable size
274
    cv::Mat medianBlurredImage;
×
275
    int filter_size = options.median_filter_size;
×
276
    // Ensure filter size is odd and within valid range
277
    if (filter_size % 2 == 0) filter_size += 1;
×
278
    if (filter_size < 3) filter_size = 3;
×
279
    if (filter_size > 101) filter_size = 101;
×
280
    
281
    cv::Mat grayMat;
×
282
    if (mat.channels() == 3) {
×
283
        cv::cvtColor(mat, grayMat, cv::COLOR_BGR2GRAY);
×
284
    } else {
285
        grayMat = mat.clone();
×
286
    }
287
    
288
    cv::medianBlur(grayMat, medianBlurredImage, filter_size);
×
289
    cv::Mat medianBlurredImage3Channel;
×
290
    cv::cvtColor(medianBlurredImage, medianBlurredImage3Channel, cv::COLOR_GRAY2BGR);
×
291

292
    // Create mask image from stored mask data
293
    cv::Mat maskImage = convert_vector_to_mat(mask, options.image_size);
×
294

295
    cv::Mat smoothedMask;
×
296
    cv::GaussianBlur(maskImage, smoothedMask, cv::Size(15, 15), 0);
×
297

298
    cv::threshold(smoothedMask, smoothedMask, 1, 255, cv::THRESH_BINARY);
×
299

300
    // Ensure mask has valid values for seamless cloning
301
    for (int y = 0; y < smoothedMask.rows; ++y) {
×
302
        for (int x = 0; x < smoothedMask.cols; ++x) {
×
303
            if (smoothedMask.at<uint8_t>(y, x) == 0) {
×
304
                smoothedMask.at<uint8_t>(y, x) = 1;
×
305
            }
306
        }
307
    }
308

309
    cv::Mat mask3Channel;
×
310
    cv::cvtColor(smoothedMask, mask3Channel, cv::COLOR_GRAY2BGR);
×
311

312
    cv::Mat outputImage;
×
313
    cv::Point const center(options.image_size.width / 2, options.image_size.height / 2);
×
314

315
    cv::seamlessClone(medianBlurredImage3Channel, inputImage3Channel, mask3Channel, center, outputImage, cv::NORMAL_CLONE);
×
316

317
    // Convert back to the original format
318
    if (mat.channels() == 1) {
×
319
        cv::cvtColor(outputImage, mat, cv::COLOR_BGR2GRAY);
×
320
    } else {
321
        mat = outputImage;
×
322
    }
323
}
×
324

325
void apply_colormap(cv::Mat& mat, ColormapOptions const& options) {
×
326
    if (!options.active || options.colormap == ColormapType::None) {
×
327
        return;
×
328
    }
329
    
330
    // Only apply to grayscale images
331
    if (mat.channels() != 1) {
×
332
        return;
×
333
    }
334
    
335
    cv::Mat normalized_mat;
×
336
    if (options.normalize) {
×
337
        // Normalize to 0-255 range
338
        cv::normalize(mat, normalized_mat, 0, 255, cv::NORM_MINMAX, CV_8UC1);
×
339
    } else {
340
        normalized_mat = mat.clone();
×
341
    }
342
    
343
    // Apply the colormap
344
    cv::Mat colored_mat;
×
345
    int cv_colormap = cv::COLORMAP_JET; // default
×
346
    
347
    switch (options.colormap) {
×
348
        case ColormapType::Jet:
×
349
            cv_colormap = cv::COLORMAP_JET;
×
350
            break;
×
351
        case ColormapType::Hot:
×
352
            cv_colormap = cv::COLORMAP_HOT;
×
353
            break;
×
354
        case ColormapType::Cool:
×
355
            cv_colormap = cv::COLORMAP_COOL;
×
356
            break;
×
357
        case ColormapType::Spring:
×
358
            cv_colormap = cv::COLORMAP_SPRING;
×
359
            break;
×
360
        case ColormapType::Summer:
×
361
            cv_colormap = cv::COLORMAP_SUMMER;
×
362
            break;
×
363
        case ColormapType::Autumn:
×
364
            cv_colormap = cv::COLORMAP_AUTUMN;
×
365
            break;
×
366
        case ColormapType::Winter:
×
367
            cv_colormap = cv::COLORMAP_WINTER;
×
368
            break;
×
369
        case ColormapType::Rainbow:
×
370
            cv_colormap = cv::COLORMAP_RAINBOW;
×
371
            break;
×
372
        case ColormapType::Ocean:
×
373
            cv_colormap = cv::COLORMAP_OCEAN;
×
374
            break;
×
375
        case ColormapType::Pink:
×
376
            cv_colormap = cv::COLORMAP_PINK;
×
377
            break;
×
378
        case ColormapType::HSV:
×
379
            cv_colormap = cv::COLORMAP_HSV;
×
380
            break;
×
381
        case ColormapType::Parula:
×
382
            cv_colormap = cv::COLORMAP_PARULA;
×
383
            break;
×
384
        case ColormapType::Viridis:
×
385
            cv_colormap = cv::COLORMAP_VIRIDIS;
×
386
            break;
×
387
        case ColormapType::Plasma:
×
388
            cv_colormap = cv::COLORMAP_PLASMA;
×
389
            break;
×
390
        case ColormapType::Inferno:
×
391
            cv_colormap = cv::COLORMAP_INFERNO;
×
392
            break;
×
393
        case ColormapType::Magma:
×
394
            cv_colormap = cv::COLORMAP_MAGMA;
×
395
            break;
×
396
        case ColormapType::Turbo:
×
397
            cv_colormap = cv::COLORMAP_TURBO;
×
398
            break;
×
399
        default:
×
400
            cv_colormap = cv::COLORMAP_JET;
×
401
            break;
×
402
    }
403
    
404
    cv::applyColorMap(normalized_mat, colored_mat, cv_colormap);
×
405
    
406
    // Apply alpha blending if needed
407
    if (options.alpha < 1.0) {
×
408
        // Convert original grayscale to BGR for blending
409
        cv::Mat gray_bgr;
×
410
        cv::cvtColor(normalized_mat, gray_bgr, cv::COLOR_GRAY2BGR);
×
411
        
412
        // Blend colored image with grayscale
413
        cv::addWeighted(colored_mat, options.alpha, gray_bgr, 1.0 - options.alpha, 0, colored_mat);
×
414
    }
×
415
    
416
    // Return BGR format (not BGRA) to maintain compatibility
417
    mat = colored_mat;
×
418
}
×
419

420
/**
421
 * @brief Apply colormap to grayscale data for display purposes only
422
 * @param grayscale_data Input grayscale image data
423
 * @param image_size Dimensions of the image
424
 * @param options Colormap options
425
 * @return BGR image data if colormap is applied, empty vector if not
426
 */
427
std::vector<uint8_t> apply_colormap_for_display(std::vector<uint8_t> const& grayscale_data, 
×
428
                                               ImageSize image_size,
429
                                               ColormapOptions const& options) {
430
    if (!options.active || options.colormap == ColormapType::None) {
×
431
        return {}; // Return empty vector to indicate no colormap applied
×
432
    }
433
    
434
    // Create cv::Mat from grayscale data
435
    cv::Mat gray_mat(image_size.height, image_size.width, CV_8UC1, 
×
436
                     const_cast<uint8_t*>(grayscale_data.data()));
×
437
    
438
    cv::Mat normalized_mat;
×
439
    if (options.normalize) {
×
440
        cv::normalize(gray_mat, normalized_mat, 0, 255, cv::NORM_MINMAX, CV_8UC1);
×
441
    } else {
442
        normalized_mat = gray_mat.clone();
×
443
    }
444
    
445
    // Apply the colormap
446
    cv::Mat colored_mat;
×
447
    int cv_colormap = cv::COLORMAP_JET; // default
×
448
    
449
    switch (options.colormap) {
×
450
        case ColormapType::Jet:
×
451
            cv_colormap = cv::COLORMAP_JET;
×
452
            break;
×
453
        case ColormapType::Hot:
×
454
            cv_colormap = cv::COLORMAP_HOT;
×
455
            break;
×
456
        case ColormapType::Cool:
×
457
            cv_colormap = cv::COLORMAP_COOL;
×
458
            break;
×
459
        case ColormapType::Spring:
×
460
            cv_colormap = cv::COLORMAP_SPRING;
×
461
            break;
×
462
        case ColormapType::Summer:
×
463
            cv_colormap = cv::COLORMAP_SUMMER;
×
464
            break;
×
465
        case ColormapType::Autumn:
×
466
            cv_colormap = cv::COLORMAP_AUTUMN;
×
467
            break;
×
468
        case ColormapType::Winter:
×
469
            cv_colormap = cv::COLORMAP_WINTER;
×
470
            break;
×
471
        case ColormapType::Rainbow:
×
472
            cv_colormap = cv::COLORMAP_RAINBOW;
×
473
            break;
×
474
        case ColormapType::Ocean:
×
475
            cv_colormap = cv::COLORMAP_OCEAN;
×
476
            break;
×
477
        case ColormapType::Pink:
×
478
            cv_colormap = cv::COLORMAP_PINK;
×
479
            break;
×
480
        case ColormapType::HSV:
×
481
            cv_colormap = cv::COLORMAP_HSV;
×
482
            break;
×
483
        case ColormapType::Parula:
×
484
            cv_colormap = cv::COLORMAP_PARULA;
×
485
            break;
×
486
        case ColormapType::Viridis:
×
487
            cv_colormap = cv::COLORMAP_VIRIDIS;
×
488
            break;
×
489
        case ColormapType::Plasma:
×
490
            cv_colormap = cv::COLORMAP_PLASMA;
×
491
            break;
×
492
        case ColormapType::Inferno:
×
493
            cv_colormap = cv::COLORMAP_INFERNO;
×
494
            break;
×
495
        case ColormapType::Magma:
×
496
            cv_colormap = cv::COLORMAP_MAGMA;
×
497
            break;
×
498
        case ColormapType::Turbo:
×
499
            cv_colormap = cv::COLORMAP_TURBO;
×
500
            break;
×
501
        default:
×
502
            cv_colormap = cv::COLORMAP_JET;
×
503
            break;
×
504
    }
505
    
506
    cv::applyColorMap(normalized_mat, colored_mat, cv_colormap);
×
507
    
508
    // Apply alpha blending if needed
509
    if (options.alpha < 1.0) {
×
510
        cv::Mat gray_bgr;
×
511
        cv::cvtColor(normalized_mat, gray_bgr, cv::COLOR_GRAY2BGR);
×
512
        cv::addWeighted(colored_mat, options.alpha, gray_bgr, 1.0 - options.alpha, 0, colored_mat);
×
513
    }
×
514
    
515
    // Convert to BGRA for Qt display
516
    cv::Mat bgra_mat;
×
517
    cv::cvtColor(colored_mat, bgra_mat, cv::COLOR_BGR2BGRA);
×
518
    
519
    // Convert to vector
520
    std::vector<uint8_t> result(bgra_mat.total() * bgra_mat.elemSize());
×
521
    std::memcpy(result.data(), bgra_mat.data, result.size());
×
522
    
523
    return result;
×
524
}
×
525

526
} // namespace ImageProcessing
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