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

paulmthompson / WhiskerToolbox / 17385470391

01 Sep 2025 07:05PM UTC coverage: 71.734% (-0.2%) from 71.938%
17385470391

push

github

paulmthompson
can combine multiple colormaps

31764 of 44280 relevant lines covered (71.73%)

1396.53 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) {
×
103
    mat.convertTo(mat, -1, options.alpha, options.beta);
×
104
}
×
105

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

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

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

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

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

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

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

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

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

207
    cv::Mat maskImage = convert_vector_to_mat(mask_copy, image_size);
×
208

209
    cv::Mat smoothedMask;
×
210
    cv::GaussianBlur(maskImage, smoothedMask, cv::Size(15, 15), 0);
×
211

212
    cv::threshold(smoothedMask, smoothedMask, 1, 255, cv::THRESH_BINARY);
×
213

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

222
    cv::Mat mask3Channel;
×
223
    cv::cvtColor(smoothedMask, mask3Channel, cv::COLOR_GRAY2BGR);
×
224

225
    cv::Mat outputImage;
×
226
    cv::Point const center(image_size.width / 2, image_size.height / 2);
×
227

228
    cv::seamlessClone(medianBlurredImage3Channel, inputImage3Channel, mask3Channel, center, outputImage, cv::NORMAL_CLONE);
×
229

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

234
    convert_mat_to_vector(output, outputImageGray, image_size);
×
235

236
    return output;
×
237
}
×
238

239
void apply_magic_eraser(cv::Mat& mat, MagicEraserOptions const& options) {
×
240

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

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

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

283
    cv::Mat smoothedMask;
×
284
    cv::GaussianBlur(maskImage, smoothedMask, cv::Size(15, 15), 0);
×
285

286
    cv::threshold(smoothedMask, smoothedMask, 1, 255, cv::THRESH_BINARY);
×
287

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

297
    cv::Mat mask3Channel;
×
298
    cv::cvtColor(smoothedMask, mask3Channel, cv::COLOR_GRAY2BGR);
×
299

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

303
    cv::seamlessClone(medianBlurredImage3Channel, inputImage3Channel, mask3Channel, center, outputImage, cv::NORMAL_CLONE);
×
304

305
    // Convert back to the original format
306
    if (mat.channels() == 1) {
×
307
        cv::cvtColor(outputImage, mat, cv::COLOR_BGR2GRAY);
×
308
    } else {
309
        mat = outputImage;
×
310
    }
311
}
×
312

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

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

514
} // 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