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

paulmthompson / WhiskerToolbox / 17410599011

02 Sep 2025 04:52PM UTC coverage: 71.464% (+0.07%) from 71.394%
17410599011

push

github

paulmthompson
single channel colormaps added

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

75 existing lines in 4 files now uncovered.

31905 of 44645 relevant lines covered (71.46%)

1385.34 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
/**
102
 * @brief Apply single-color channel mapping to grayscale image
103
 * @param input_mat Input grayscale matrix (8-bit)
104
 * @param output_mat Output BGR colored matrix
105
 * @param colormap_type The single-color colormap type
106
 */
NEW
107
static void _applySingleColorMapping(cv::Mat const& input_mat, cv::Mat& output_mat, ColormapType colormap_type) {
×
108
    // Create a 3-channel BGR image
NEW
109
    output_mat = cv::Mat::zeros(input_mat.rows, input_mat.cols, CV_8UC3);
×
110
    
111
    // Define the color channel values (BGR format)
NEW
112
    cv::Vec3b color_values;
×
113
    
NEW
114
    switch (colormap_type) {
×
NEW
115
        case ColormapType::Red:
×
NEW
116
            color_values = cv::Vec3b(0, 0, 255);    // Red in BGR
×
NEW
117
            break;
×
NEW
118
        case ColormapType::Green:
×
NEW
119
            color_values = cv::Vec3b(0, 255, 0);    // Green in BGR
×
NEW
120
            break;
×
NEW
121
        case ColormapType::Blue:
×
NEW
122
            color_values = cv::Vec3b(255, 0, 0);    // Blue in BGR
×
NEW
123
            break;
×
NEW
124
        case ColormapType::Cyan:
×
NEW
125
            color_values = cv::Vec3b(255, 255, 0);  // Cyan in BGR
×
NEW
126
            break;
×
NEW
127
        case ColormapType::Magenta:
×
NEW
128
            color_values = cv::Vec3b(255, 0, 255);  // Magenta in BGR
×
NEW
129
            break;
×
NEW
130
        case ColormapType::Yellow:
×
NEW
131
            color_values = cv::Vec3b(0, 255, 255);  // Yellow in BGR
×
NEW
132
            break;
×
NEW
133
        default:
×
NEW
134
            color_values = cv::Vec3b(0, 0, 255);    // Default to red
×
NEW
135
            break;
×
136
    }
137
    
138
    // Apply the color mapping
NEW
139
    for (int y = 0; y < input_mat.rows; ++y) {
×
NEW
140
        for (int x = 0; x < input_mat.cols; ++x) {
×
NEW
141
            uint8_t intensity = input_mat.at<uint8_t>(y, x);
×
142
            
143
            // Scale each channel by the intensity
NEW
144
            cv::Vec3b& pixel = output_mat.at<cv::Vec3b>(y, x);
×
NEW
145
            pixel[0] = (color_values[0] * intensity) / 255;  // Blue channel
×
NEW
146
            pixel[1] = (color_values[1] * intensity) / 255;  // Green channel
×
NEW
147
            pixel[2] = (color_values[2] * intensity) / 255;  // Red channel
×
148
        }
149
    }
NEW
150
}
×
151

152
// New options-based implementations
153
void linear_transform(cv::Mat & mat, ContrastOptions const& options) {
×
154
    double alpha = options.alpha;
×
155
    int beta = options.beta;
×
156

157
    // Always calculate alpha and beta from min/max values for consistent behavior
158
    if (options.display_max <= options.display_min) {
×
159
        alpha = 1.0;
×
160
        beta = 0;
×
161
    } else {
162
        alpha = 255.0 / (options.display_max - options.display_min);
×
163
        beta = static_cast<int>(-alpha * options.display_min);
×
164
    }
165

166
    mat.convertTo(mat, -1, alpha, beta);
×
167
}
×
168

169
void gamma_transform(cv::Mat & mat, GammaOptions const& options) {
×
170
    if (mat.depth() == CV_8U) {
×
171
        // Original 8-bit implementation using lookup table
172
        cv::Mat lookupTable(1, 256, CV_8U);
×
173
        uchar* p = lookupTable.ptr();
×
174
        for (int i = 0; i < 256; ++i) {
×
175
            p[i] = cv::saturate_cast<uchar>(pow(i / 255.0, options.gamma) * 255.0);
×
176
        }
177
        cv::LUT(mat, lookupTable, mat);
×
178
    } else if (mat.depth() == CV_32F) {
×
179
        // 32-bit float implementation using direct computation
180
        // For float data, we assume it's normalized to 0-255 range
181
        mat.forEach<float>([&options](float& pixel, const int* /*position*/) {
×
182
            // Normalize to 0-1 range, apply gamma, then scale back to 0-255
183
            float normalized = pixel / 255.0f;
×
184
            normalized = std::max(0.0f, std::min(1.0f, normalized)); // Clamp to valid range
×
185
            pixel = std::pow(normalized, options.gamma) * 255.0f;
×
186
        });
×
187
    } else {
188
        // For other bit depths, convert to 8-bit temporarily, apply gamma, then convert back
189
        cv::Mat temp;
×
190
        mat.convertTo(temp, CV_8U);
×
191
        
192
        cv::Mat lookupTable(1, 256, CV_8U);
×
193
        uchar* p = lookupTable.ptr();
×
194
        for (int i = 0; i < 256; ++i) {
×
195
            p[i] = cv::saturate_cast<uchar>(pow(i / 255.0, options.gamma) * 255.0);
×
196
        }
197
        cv::LUT(temp, lookupTable, temp);
×
198
        
199
        temp.convertTo(mat, mat.depth());
×
200
    }
×
201
}
×
202

203
void clahe(cv::Mat & mat, ClaheOptions const& options) {
×
204
    if (mat.depth() == CV_8U) {
×
205
        // CLAHE works directly with 8-bit data
206
        cv::Ptr<cv::CLAHE> clahe_ptr = cv::createCLAHE(options.clip_limit, cv::Size(options.grid_size, options.grid_size));
×
207
        clahe_ptr->apply(mat, mat);
×
208
    } else if (mat.depth() == CV_32F) {
×
209
        // For 32-bit float, convert to 8-bit, apply CLAHE, then convert back
210
        cv::Mat temp_8bit;
×
211
        mat.convertTo(temp_8bit, CV_8U);
×
212
        
213
        cv::Ptr<cv::CLAHE> clahe_ptr = cv::createCLAHE(options.clip_limit, cv::Size(options.grid_size, options.grid_size));
×
214
        clahe_ptr->apply(temp_8bit, temp_8bit);
×
215
        
216
        temp_8bit.convertTo(mat, CV_32F);
×
217
    } else {
×
218
        // For other bit depths, convert to 8-bit, apply CLAHE, then convert back
219
        cv::Mat temp_8bit;
×
220
        mat.convertTo(temp_8bit, CV_8U);
×
221
        
222
        cv::Ptr<cv::CLAHE> clahe_ptr = cv::createCLAHE(options.clip_limit, cv::Size(options.grid_size, options.grid_size));
×
223
        clahe_ptr->apply(temp_8bit, temp_8bit);
×
224
        
225
        temp_8bit.convertTo(mat, mat.depth());
×
226
    }
×
227
}
×
228

229
void sharpen_image(cv::Mat & mat, SharpenOptions const& options) {
×
230
    cv::Mat blurred;
×
231
    cv::GaussianBlur(mat, blurred, cv::Size(0, 0), options.sigma);
×
232
    cv::addWeighted(mat, 1.0 + 1.0, blurred, -1.0, 0, mat);
×
233
}
×
234

235
void bilateral_filter(cv::Mat & mat, BilateralOptions const& options) {
×
236
    cv::Mat temp;
×
237
    cv::bilateralFilter(mat, temp, options.diameter, options.sigma_color, options.sigma_spatial);
×
238
    mat = temp;
×
239
}
×
240

241
void median_filter(cv::Mat & mat, MedianOptions const& options) {
×
242
    if (options.kernel_size >= 3 && options.kernel_size % 2 == 1) {
×
243
        cv::medianBlur(mat, mat, options.kernel_size);
×
244
    }
245
}
×
246

247
std::vector<Point2D<uint32_t>> dilate_mask(std::vector<Point2D<uint32_t>> const& mask, ImageSize image_size, MaskDilationOptions const& options) {
×
248
    if (mask.empty() || !options.active) {
×
249
        return mask;
×
250
    }
251
    
252
    // Convert point-based mask to cv::Mat
253
    cv::Mat mask_mat = cv::Mat::zeros(image_size.height, image_size.width, CV_8UC1);
×
254
    
255
    // Fill the mask matrix with points
256
    for (auto const& point : mask) {
×
257
        int x = static_cast<int>(std::round(point.x));
×
258
        int y = static_cast<int>(std::round(point.y));
×
259
        if (x >= 0 && x < image_size.width && y >= 0 && y < image_size.height) {
×
260
            mask_mat.at<uint8_t>(y, x) = 255;
×
261
        }
262
    }
263
    
264
    // Apply dilation/erosion
265
    dilate_mask_mat(mask_mat, options);
×
266
    
267
    // Convert back to point-based representation
268
    return create_mask(mask_mat);
×
269
}
×
270

271
void dilate_mask_mat(cv::Mat& mat, MaskDilationOptions const& options) {
×
272
    if (!options.active) {
×
273
        return;
×
274
    }
275
    
276
    int kernel_size = options.is_grow_mode ? options.grow_size : options.shrink_size;
×
277
    
278
    if (kernel_size <= 0) {
×
279
        return;
×
280
    }
281
    
282
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(kernel_size, kernel_size));
×
283
    
284
    if (options.is_grow_mode) {
×
285
        cv::dilate(mat, mat, kernel);
×
286
    } else {
287
        cv::erode(mat, mat, kernel);
×
288
    }
289
}
×
290

291
std::vector<uint8_t> apply_magic_eraser_with_options(std::vector<uint8_t> const& image, 
×
292
                                                    ImageSize image_size, 
293
                                                    std::vector<uint8_t> const& mask,
294
                                                    MagicEraserOptions const& options) {
295
    // Create mutable copies for the conversion functions
296
    std::vector<uint8_t> image_copy = image;
×
297
    std::vector<uint8_t> mask_copy = mask;
×
298
    
299
    // Convert the input vector to a cv::Mat
300
    cv::Mat inputImage = convert_vector_to_mat(image_copy, image_size);
×
301
    cv::Mat inputImage3Channel;
×
302
    cv::cvtColor(inputImage, inputImage3Channel, cv::COLOR_GRAY2BGR);
×
303

304
    // Apply median blur filter with configurable size
305
    cv::Mat medianBlurredImage;
×
306
    int filter_size = options.median_filter_size;
×
307
    // Ensure filter size is odd and within valid range
308
    if (filter_size % 2 == 0) filter_size += 1;
×
309
    if (filter_size < 3) filter_size = 3;
×
310
    if (filter_size > 101) filter_size = 101;
×
311
    
312
    cv::medianBlur(inputImage, medianBlurredImage, filter_size);
×
313
    cv::Mat medianBlurredImage3Channel;
×
314
    cv::cvtColor(medianBlurredImage, medianBlurredImage3Channel, cv::COLOR_GRAY2BGR);
×
315

316
    cv::Mat maskImage = convert_vector_to_mat(mask_copy, image_size);
×
317

318
    cv::Mat smoothedMask;
×
319
    cv::GaussianBlur(maskImage, smoothedMask, cv::Size(15, 15), 0);
×
320

321
    cv::threshold(smoothedMask, smoothedMask, 1, 255, cv::THRESH_BINARY);
×
322

323
    for (int y = 0; y < smoothedMask.rows; ++y) {
×
324
        for (int x = 0; x < smoothedMask.cols; ++x) {
×
325
            if (smoothedMask.at<uint8_t>(y, x) == 0) {
×
326
                smoothedMask.at<uint8_t>(y, x) = 1;
×
327
            }
328
        }
329
    }
330

331
    cv::Mat mask3Channel;
×
332
    cv::cvtColor(smoothedMask, mask3Channel, cv::COLOR_GRAY2BGR);
×
333

334
    cv::Mat outputImage;
×
335
    cv::Point const center(image_size.width / 2, image_size.height / 2);
×
336

337
    cv::seamlessClone(medianBlurredImage3Channel, inputImage3Channel, mask3Channel, center, outputImage, cv::NORMAL_CLONE);
×
338

339
    cv::Mat outputImageGray;
×
340
    cv::cvtColor(outputImage, outputImageGray, cv::COLOR_BGR2GRAY);
×
341
    auto output = std::vector<uint8_t>(static_cast<size_t>(image_size.height * image_size.width));
×
342

343
    convert_mat_to_vector(output, outputImageGray, image_size);
×
344

345
    return output;
×
346
}
×
347

348
void apply_magic_eraser(cv::Mat& mat, MagicEraserOptions const& options) {
×
349

350
    auto mask = options.mask;
×
351
    // Only apply if we have a valid mask
352
    if (mask.empty() || options.image_size.width <= 0 || options.image_size.height <= 0) {
×
353
        return;
×
354
    }
355
    
356
    // Check if the image dimensions match the stored mask dimensions
357
    if (mat.rows != options.image_size.height || mat.cols != options.image_size.width) {
×
358
        std::cerr << "Magic eraser: Image dimensions don't match stored mask dimensions" << std::endl;
×
359
        return;
×
360
    }
361
    
362
    // Convert the image to 3-channel for seamless cloning
363
    cv::Mat inputImage3Channel;
×
364
    if (mat.channels() == 1) {
×
365
        cv::cvtColor(mat, inputImage3Channel, cv::COLOR_GRAY2BGR);
×
366
    } else {
367
        inputImage3Channel = mat.clone();
×
368
    }
369

370
    // Apply median blur filter with configurable size
371
    cv::Mat medianBlurredImage;
×
372
    int filter_size = options.median_filter_size;
×
373
    // Ensure filter size is odd and within valid range
374
    if (filter_size % 2 == 0) filter_size += 1;
×
375
    if (filter_size < 3) filter_size = 3;
×
376
    if (filter_size > 101) filter_size = 101;
×
377
    
378
    cv::Mat grayMat;
×
379
    if (mat.channels() == 3) {
×
380
        cv::cvtColor(mat, grayMat, cv::COLOR_BGR2GRAY);
×
381
    } else {
382
        grayMat = mat.clone();
×
383
    }
384
    
385
    cv::medianBlur(grayMat, medianBlurredImage, filter_size);
×
386
    cv::Mat medianBlurredImage3Channel;
×
387
    cv::cvtColor(medianBlurredImage, medianBlurredImage3Channel, cv::COLOR_GRAY2BGR);
×
388

389
    // Create mask image from stored mask data
390
    cv::Mat maskImage = convert_vector_to_mat(mask, options.image_size);
×
391

392
    cv::Mat smoothedMask;
×
393
    cv::GaussianBlur(maskImage, smoothedMask, cv::Size(15, 15), 0);
×
394

395
    cv::threshold(smoothedMask, smoothedMask, 1, 255, cv::THRESH_BINARY);
×
396

397
    // Ensure mask has valid values for seamless cloning
398
    for (int y = 0; y < smoothedMask.rows; ++y) {
×
399
        for (int x = 0; x < smoothedMask.cols; ++x) {
×
400
            if (smoothedMask.at<uint8_t>(y, x) == 0) {
×
401
                smoothedMask.at<uint8_t>(y, x) = 1;
×
402
            }
403
        }
404
    }
405

406
    cv::Mat mask3Channel;
×
407
    cv::cvtColor(smoothedMask, mask3Channel, cv::COLOR_GRAY2BGR);
×
408

409
    cv::Mat outputImage;
×
410
    cv::Point const center(options.image_size.width / 2, options.image_size.height / 2);
×
411

412
    cv::seamlessClone(medianBlurredImage3Channel, inputImage3Channel, mask3Channel, center, outputImage, cv::NORMAL_CLONE);
×
413

414
    // Convert back to the original format
415
    if (mat.channels() == 1) {
×
416
        cv::cvtColor(outputImage, mat, cv::COLOR_BGR2GRAY);
×
417
    } else {
418
        mat = outputImage;
×
419
    }
420
}
×
421

422
void apply_colormap(cv::Mat& mat, ColormapOptions const& options) {
×
423
    if (!options.active || options.colormap == ColormapType::None) {
×
424
        return;
×
425
    }
426
    
427
    // Only apply to grayscale images
428
    if (mat.channels() != 1) {
×
429
        return;
×
430
    }
431
    
432
    cv::Mat normalized_mat;
×
433
    if (options.normalize) {
×
434
        // Normalize to 0-255 range and convert to 8-bit for colormap application
435
        cv::normalize(mat, normalized_mat, 0, 255, cv::NORM_MINMAX, CV_8UC1);
×
436
    } else {
437
        // Convert to 8-bit if necessary for colormap application
438
        if (mat.depth() == CV_8U) {
×
439
            normalized_mat = mat.clone();
×
440
        } else {
441
            mat.convertTo(normalized_mat, CV_8U);
×
442
        }
443
    }
444
    
UNCOV
445
    cv::Mat colored_mat;
×
446
    
447
    // Check if it's a single-color channel mapping
NEW
448
    if (options.colormap == ColormapType::Red || options.colormap == ColormapType::Green || 
×
NEW
449
        options.colormap == ColormapType::Blue || options.colormap == ColormapType::Cyan ||
×
NEW
450
        options.colormap == ColormapType::Magenta || options.colormap == ColormapType::Yellow) {
×
451
        
452
        // Apply single-color channel mapping
NEW
453
        _applySingleColorMapping(normalized_mat, colored_mat, options.colormap);
×
454
    } else {
455
        // Apply standard OpenCV colormap
NEW
456
        int cv_colormap = cv::COLORMAP_JET; // default
×
457
        
NEW
458
        switch (options.colormap) {
×
NEW
459
            case ColormapType::Jet:
×
NEW
460
                cv_colormap = cv::COLORMAP_JET;
×
NEW
461
                break;
×
NEW
462
            case ColormapType::Hot:
×
NEW
463
                cv_colormap = cv::COLORMAP_HOT;
×
NEW
464
                break;
×
NEW
465
            case ColormapType::Cool:
×
NEW
466
                cv_colormap = cv::COLORMAP_COOL;
×
NEW
467
                break;
×
NEW
468
            case ColormapType::Spring:
×
NEW
469
                cv_colormap = cv::COLORMAP_SPRING;
×
NEW
470
                break;
×
NEW
471
            case ColormapType::Summer:
×
NEW
472
                cv_colormap = cv::COLORMAP_SUMMER;
×
NEW
473
                break;
×
NEW
474
            case ColormapType::Autumn:
×
NEW
475
                cv_colormap = cv::COLORMAP_AUTUMN;
×
NEW
476
                break;
×
NEW
477
            case ColormapType::Winter:
×
NEW
478
                cv_colormap = cv::COLORMAP_WINTER;
×
NEW
479
                break;
×
NEW
480
            case ColormapType::Rainbow:
×
NEW
481
                cv_colormap = cv::COLORMAP_RAINBOW;
×
NEW
482
                break;
×
NEW
483
            case ColormapType::Ocean:
×
NEW
484
                cv_colormap = cv::COLORMAP_OCEAN;
×
NEW
485
                break;
×
NEW
486
            case ColormapType::Pink:
×
NEW
487
                cv_colormap = cv::COLORMAP_PINK;
×
NEW
488
                break;
×
NEW
489
            case ColormapType::HSV:
×
NEW
490
                cv_colormap = cv::COLORMAP_HSV;
×
NEW
491
                break;
×
NEW
492
            case ColormapType::Parula:
×
NEW
493
                cv_colormap = cv::COLORMAP_PARULA;
×
NEW
494
                break;
×
NEW
495
            case ColormapType::Viridis:
×
NEW
496
                cv_colormap = cv::COLORMAP_VIRIDIS;
×
NEW
497
                break;
×
NEW
498
            case ColormapType::Plasma:
×
NEW
499
                cv_colormap = cv::COLORMAP_PLASMA;
×
NEW
500
                break;
×
NEW
501
            case ColormapType::Inferno:
×
NEW
502
                cv_colormap = cv::COLORMAP_INFERNO;
×
NEW
503
                break;
×
NEW
504
            case ColormapType::Magma:
×
NEW
505
                cv_colormap = cv::COLORMAP_MAGMA;
×
NEW
506
                break;
×
NEW
507
            case ColormapType::Turbo:
×
NEW
508
                cv_colormap = cv::COLORMAP_TURBO;
×
NEW
509
                break;
×
NEW
510
            default:
×
NEW
511
                cv_colormap = cv::COLORMAP_JET;
×
NEW
512
                break;
×
513
        }
514
        
NEW
515
        cv::applyColorMap(normalized_mat, colored_mat, cv_colormap);
×
516
    }
517
    
518
    // Apply alpha blending if needed
UNCOV
519
    if (options.alpha < 1.0) {
×
520
        // Convert original grayscale to BGR for blending
521
        cv::Mat gray_bgr;
×
522
        cv::cvtColor(normalized_mat, gray_bgr, cv::COLOR_GRAY2BGR);
×
523
        
524
        // Blend colored image with grayscale
525
        cv::addWeighted(colored_mat, options.alpha, gray_bgr, 1.0 - options.alpha, 0, colored_mat);
×
526
    }
×
527
    
528
    // Return BGR format (not BGRA) to maintain compatibility
529
    mat = colored_mat;
×
530
}
×
531

532
/**
533
 * @brief Apply colormap to grayscale data for display purposes only
534
 * @param grayscale_data Input grayscale image data
535
 * @param image_size Dimensions of the image
536
 * @param options Colormap options
537
 * @return BGR image data if colormap is applied, empty vector if not
538
 */
539
std::vector<uint8_t> apply_colormap_for_display(std::vector<uint8_t> const& grayscale_data, 
×
540
                                               ImageSize image_size,
541
                                               ColormapOptions const& options) {
542
    if (!options.active || options.colormap == ColormapType::None) {
×
543
        return {}; // Return empty vector to indicate no colormap applied
×
544
    }
545
    
546
    // Create cv::Mat from grayscale data
547
    cv::Mat gray_mat(image_size.height, image_size.width, CV_8UC1, 
×
548
                     const_cast<uint8_t*>(grayscale_data.data()));
×
549
    
550
    cv::Mat normalized_mat;
×
551
    if (options.normalize) {
×
552
        cv::normalize(gray_mat, normalized_mat, 0, 255, cv::NORM_MINMAX, CV_8UC1);
×
553
    } else {
554
        normalized_mat = gray_mat.clone();
×
555
    }
556
    
557
    // Apply the colormap
558
    cv::Mat colored_mat;
×
559
    
560
    // Check if it's a single-color channel mapping
NEW
561
    if (options.colormap == ColormapType::Red || options.colormap == ColormapType::Green || 
×
NEW
562
        options.colormap == ColormapType::Blue || options.colormap == ColormapType::Cyan ||
×
NEW
563
        options.colormap == ColormapType::Magenta || options.colormap == ColormapType::Yellow) {
×
564
        
565
        // Apply single-color channel mapping
NEW
566
        _applySingleColorMapping(normalized_mat, colored_mat, options.colormap);
×
567
    } else {
568
        // Apply standard OpenCV colormap
NEW
569
        int cv_colormap = cv::COLORMAP_JET; // default
×
570
        
NEW
571
        switch (options.colormap) {
×
NEW
572
            case ColormapType::Jet:
×
NEW
573
                cv_colormap = cv::COLORMAP_JET;
×
NEW
574
                break;
×
NEW
575
            case ColormapType::Hot:
×
NEW
576
                cv_colormap = cv::COLORMAP_HOT;
×
NEW
577
                break;
×
NEW
578
            case ColormapType::Cool:
×
NEW
579
                cv_colormap = cv::COLORMAP_COOL;
×
NEW
580
                break;
×
NEW
581
            case ColormapType::Spring:
×
NEW
582
                cv_colormap = cv::COLORMAP_SPRING;
×
NEW
583
                break;
×
NEW
584
            case ColormapType::Summer:
×
NEW
585
                cv_colormap = cv::COLORMAP_SUMMER;
×
NEW
586
                break;
×
NEW
587
            case ColormapType::Autumn:
×
NEW
588
                cv_colormap = cv::COLORMAP_AUTUMN;
×
NEW
589
                break;
×
NEW
590
            case ColormapType::Winter:
×
NEW
591
                cv_colormap = cv::COLORMAP_WINTER;
×
NEW
592
                break;
×
NEW
593
            case ColormapType::Rainbow:
×
NEW
594
                cv_colormap = cv::COLORMAP_RAINBOW;
×
NEW
595
                break;
×
NEW
596
            case ColormapType::Ocean:
×
NEW
597
                cv_colormap = cv::COLORMAP_OCEAN;
×
NEW
598
                break;
×
NEW
599
            case ColormapType::Pink:
×
NEW
600
                cv_colormap = cv::COLORMAP_PINK;
×
NEW
601
                break;
×
NEW
602
            case ColormapType::HSV:
×
NEW
603
                cv_colormap = cv::COLORMAP_HSV;
×
NEW
604
                break;
×
NEW
605
            case ColormapType::Parula:
×
NEW
606
                cv_colormap = cv::COLORMAP_PARULA;
×
NEW
607
                break;
×
NEW
608
            case ColormapType::Viridis:
×
NEW
609
                cv_colormap = cv::COLORMAP_VIRIDIS;
×
NEW
610
                break;
×
NEW
611
            case ColormapType::Plasma:
×
NEW
612
                cv_colormap = cv::COLORMAP_PLASMA;
×
NEW
613
                break;
×
NEW
614
            case ColormapType::Inferno:
×
NEW
615
                cv_colormap = cv::COLORMAP_INFERNO;
×
NEW
616
                break;
×
NEW
617
            case ColormapType::Magma:
×
NEW
618
                cv_colormap = cv::COLORMAP_MAGMA;
×
NEW
619
                break;
×
NEW
620
            case ColormapType::Turbo:
×
NEW
621
                cv_colormap = cv::COLORMAP_TURBO;
×
NEW
622
                break;
×
NEW
623
            default:
×
NEW
624
                cv_colormap = cv::COLORMAP_JET;
×
NEW
625
                break;
×
626
        }
627
        
NEW
628
        cv::applyColorMap(normalized_mat, colored_mat, cv_colormap);
×
629
    }
630
    
631
    // Apply alpha blending if needed
UNCOV
632
    if (options.alpha < 1.0) {
×
633
        cv::Mat gray_bgr;
×
634
        cv::cvtColor(normalized_mat, gray_bgr, cv::COLOR_GRAY2BGR);
×
635
        cv::addWeighted(colored_mat, options.alpha, gray_bgr, 1.0 - options.alpha, 0, colored_mat);
×
636
    }
×
637
    
638
    // Convert to BGRA for Qt display
639
    cv::Mat bgra_mat;
×
640
    cv::cvtColor(colored_mat, bgra_mat, cv::COLOR_BGR2BGRA);
×
641
    
642
    // Convert to vector
643
    std::vector<uint8_t> result(bgra_mat.total() * bgra_mat.elemSize());
×
644
    std::memcpy(result.data(), bgra_mat.data, result.size());
×
645
    
646
    return result;
×
647
}
×
648

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