5
\$\begingroup\$

This is a follow-up question for An Updated Multi-dimensional Image Data Structure with Variadic Template Functions in C++, histogram Template Function Implementation for Image in C++, Histogram of Image using std::map in C++, histogram_normalized and histogram_with_bins Template Functions Implementation for Image in C++, normalize_histogram Template Function Implementation for Image in C++ and otsu_threshold Template Function Implementation for Image in C++.In G. Sliepen's answer menthoned that:

Create a class Histogram that hides how the histogram is actually stored internally, and provides a uniform way to access that data.

I created a class Histogram that includes getCount and addCount member functions.

The experimental implementation

  • Histogram class implementation

    namespace TinyDIP
    {
     template<class ElementT>
     class Histogram
     {
     private:
     std::map<ElementT, std::size_t> histogram;
     public:
     Histogram() = default;
     Histogram(const std::map<ElementT, std::size_t>& input)
     {
     histogram = input;
     }
     constexpr std::size_t getCount(const ElementT& input)
     {
     if (histogram.contains(input))
     {
     return histogram.at(input);
     }
     else
     {
     return std::size_t{ 0 };
     }
     }
     constexpr Histogram& addCount(const ElementT& input)
     {
     if (histogram.contains(input))
     {
     ++histogram[input];
     }
     else
     {
     histogram.emplace(input, std::size_t{ 1 });
     }
     return *this;
     }
     using iterator = typename std::map<ElementT, std::size_t>::iterator;
     using const_iterator =
     typename std::map<ElementT, std::size_t>::const_iterator;
     const_iterator cbegin() const { return histogram.cbegin(); }
     const_iterator cend() const { return histogram.cend(); }
     const_iterator begin() const { return histogram.cbegin(); }
     const_iterator end() const { return histogram.cend(); }
     iterator begin() { return histogram.begin(); }
     iterator end() { return histogram.end(); }
     // + operator to add two Histograms
     Histogram operator+(const Histogram& other) const
     {
     Histogram result = *this;
     for (const auto& [key, value] : other.histogram)
     {
     result.histogram[key] += value;
     }
     return result;
     }
     // - operator to subtract two Histograms
     Histogram operator-(const Histogram& other) const
     {
     Histogram result = *this;
     for (const auto& [key, value] : other.histogram) {
     if (result.histogram.contains(key)) {
     if (result.histogram[key] >= value) {
     result.histogram[key] -= value;
     }
     else {
     result.histogram[key] = 0;
     }
     }
     }
     return result;
     }
     };
    }
    

The usage of Histogram class:

// histogram template function implementation
// https://codereview.stackexchange.com/q/295448/231235
template<class ElementT = int>
constexpr static auto histogram(const Image<ElementT>& input)
{
 Histogram<ElementT> output;
 auto image_data = input.getImageData();
 for (std::size_t i = 0; i < image_data.size(); ++i)
 {
 output.addCount(image_data[i]);
 }
 return output;
}

The main function:

/* Developed by Jimmy Hu */
#include <chrono>
#include <ostream>
#include "../base_types.h"
#include "../basic_functions.h"
#include "../image.h"
#include "../image_io.h"
#include "../image_operations.h"
#include "../timer.h"
template<class ExPo, class ElementT>
requires (std::is_execution_policy_v<std::remove_cvref_t<ExPo>>)
constexpr static auto HistogramTest(
 ExPo execution_policy,
 const TinyDIP::Image<ElementT>& input,
 std::ostream& os = std::cout
)
{
 auto hsv_image = TinyDIP::rgb2hsv(execution_policy, input);
 auto v_plane = TinyDIP::getVplane(hsv_image);
 TinyDIP::Timer timer1;
 os << "***** histogram of the image *****\n";
 auto histogram_result = TinyDIP::histogram(v_plane);
 double sum = 0.0;
 for (const auto& [key, value] : histogram_result)
 {
 os << "key = " << key << ", value = " << value << '\n';
 sum += value;
 }
 os << "sum = " << sum << '\n';
 
 return;
}
int main()
{
 TinyDIP::Timer timer1;
 std::string image_filename = "../InputImages/1.bmp";
 auto image_input = TinyDIP::bmp_read(image_filename.c_str(), true);
 image_input = TinyDIP::copyResizeBicubic(image_input, 3 * image_input.getWidth(), 3 * image_input.getHeight());
 HistogramTest(std::execution::par, image_input);
 return EXIT_SUCCESS;
}

The output of the test code above:

Width of the input image: 512
Height of the input image: 512
Size of the input image(Byte): 786432
***** histogram of the image *****
key = 46, value = 1
key = 48, value = 2
key = 49, value = 1
key = 50, value = 5
key = 51, value = 3
key = 52, value = 6
key = 53, value = 7
key = 54, value = 10
key = 55, value = 12
key = 56, value = 16
key = 57, value = 33
key = 58, value = 58
key = 59, value = 66
key = 60, value = 126
key = 61, value = 153
key = 62, value = 224
key = 63, value = 295
key = 64, value = 404
key = 65, value = 502
key = 66, value = 580
key = 67, value = 772
key = 68, value = 1000
key = 69, value = 1169
key = 70, value = 1531
key = 71, value = 1784
key = 72, value = 2001
key = 73, value = 2410
key = 74, value = 2763
key = 75, value = 3208
key = 76, value = 3631
key = 77, value = 4275
key = 78, value = 4834
key = 79, value = 5505
key = 80, value = 6232
key = 81, value = 6968
key = 82, value = 7840
key = 83, value = 9120
key = 84, value = 9836
key = 85, value = 10661
key = 86, value = 11600
key = 87, value = 12338
key = 88, value = 13199
key = 89, value = 13437
key = 90, value = 13590
key = 91, value = 13943
key = 92, value = 14053
key = 93, value = 14216
key = 94, value = 13968
key = 95, value = 14044
key = 96, value = 13882
key = 97, value = 13967
key = 98, value = 13409
key = 99, value = 13215
key = 100, value = 12688
key = 101, value = 11978
key = 102, value = 11292
key = 103, value = 10905
key = 104, value = 10115
key = 105, value = 9597
key = 106, value = 8496
key = 107, value = 8222
key = 108, value = 7606
key = 109, value = 7260
key = 110, value = 6784
key = 111, value = 6434
key = 112, value = 6119
key = 113, value = 5888
key = 114, value = 5709
key = 115, value = 5609
key = 116, value = 5448
key = 117, value = 5294
key = 118, value = 5106
key = 119, value = 5081
key = 120, value = 4972
key = 121, value = 4998
key = 122, value = 4943
key = 123, value = 5104
key = 124, value = 5015
key = 125, value = 5101
key = 126, value = 5184
key = 127, value = 5235
key = 128, value = 5283
key = 129, value = 5358
key = 130, value = 5451
key = 131, value = 5493
key = 132, value = 5631
key = 133, value = 5828
key = 134, value = 6011
key = 135, value = 5833
key = 136, value = 6042
key = 137, value = 6000
key = 138, value = 6138
key = 139, value = 6298
key = 140, value = 6390
key = 141, value = 6501
key = 142, value = 6500
key = 143, value = 6614
key = 144, value = 6635
key = 145, value = 6923
key = 146, value = 6678
key = 147, value = 6813
key = 148, value = 6730
key = 149, value = 6909
key = 150, value = 7063
key = 151, value = 7219
key = 152, value = 7233
key = 153, value = 7520
key = 154, value = 7565
key = 155, value = 7881
key = 156, value = 8026
key = 157, value = 8114
key = 158, value = 7960
key = 159, value = 8703
key = 160, value = 8383
key = 161, value = 8620
key = 162, value = 8615
key = 163, value = 8890
key = 164, value = 8878
key = 165, value = 9275
key = 166, value = 9317
key = 167, value = 9998
key = 168, value = 10437
key = 169, value = 11598
key = 170, value = 12296
key = 171, value = 13328
key = 172, value = 14616
key = 173, value = 15616
key = 174, value = 16266
key = 175, value = 16644
key = 176, value = 16349
key = 177, value = 15965
key = 178, value = 15098
key = 179, value = 14668
key = 180, value = 13547
key = 181, value = 13298
key = 182, value = 13014
key = 183, value = 12802
key = 184, value = 12914
key = 185, value = 13299
key = 186, value = 13307
key = 187, value = 13829
key = 188, value = 14569
key = 189, value = 15167
key = 190, value = 15472
key = 191, value = 16155
key = 192, value = 17008
key = 193, value = 16731
key = 194, value = 16807
key = 195, value = 17156
key = 196, value = 16915
key = 197, value = 17381
key = 198, value = 17651
key = 199, value = 18499
key = 200, value = 19264
key = 201, value = 20976
key = 202, value = 22612
key = 203, value = 24892
key = 204, value = 26546
key = 205, value = 29127
key = 206, value = 29949
key = 207, value = 30658
key = 208, value = 30772
key = 209, value = 30274
key = 210, value = 28422
key = 211, value = 27030
key = 212, value = 25470
key = 213, value = 24549
key = 214, value = 24140
key = 215, value = 23917
key = 216, value = 24793
key = 217, value = 26640
key = 218, value = 28707
key = 219, value = 31407
key = 220, value = 34373
key = 221, value = 36521
key = 222, value = 37484
key = 223, value = 37724
key = 224, value = 35813
key = 225, value = 31366
key = 226, value = 27789
key = 227, value = 24688
key = 228, value = 22476
key = 229, value = 20738
key = 230, value = 19779
key = 231, value = 19260
key = 232, value = 18380
key = 233, value = 19396
key = 234, value = 20517
key = 235, value = 21465
key = 236, value = 21721
key = 237, value = 21324
key = 238, value = 19178
key = 239, value = 16891
key = 240, value = 15506
key = 241, value = 11898
key = 242, value = 9560
key = 243, value = 7952
key = 244, value = 6834
key = 245, value = 6631
key = 246, value = 5632
key = 247, value = 4487
key = 248, value = 3592
key = 249, value = 3271
key = 250, value = 2811
key = 251, value = 2401
key = 252, value = 1947
key = 253, value = 1269
key = 254, value = 784
key = 255, value = 884
sum = 2.3593e+06
Computation finished at Fri Mar 28 22:27:34 2025
 elapsed time: 3.8231177 seconds.
Computation finished at Fri Mar 28 22:27:34 2025
 elapsed time: 4.9043645 seconds.

TinyDIP on GitHub

All suggestions are welcome.

The summary information:

asked Mar 28 at 14:57
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

This is a neat class with a clear interface.

I would consider adding a constructor that takes an image as input, and computes the full histogram for the image.

The only issue I see with this class is that it always uses a map, even for the example uint8 image where a vector would be much more efficient (and which you’ve used in your previous posts on histogram computation). One solution is to have both a vector and a map as class members, and a Boolean that says which of the two is being used. For some histograms (eg the color image in a previous post of yours) the data is stored in a map, for others (uint8 and uint16) in a vector. This does add a test for each access, but that might be OK, and still faster than a lookup in a map. Another solution could be to have a single histogram member, but have its type be selected at compile time (see G. Sliepen’s comment below).

In this bit of code:

 if (histogram.contains(input))
 {
 return histogram.at(input);
 }

you are doing the lookup twice. Instead, use std::map::find() to get an iterator to the element. If you don’t get the end iterator back, you can dereference it read the value.

This bit of code is also redundant:

 if (histogram.contains(input))
 {
 ++histogram[input];
 }
 else
 {
 histogram.emplace(input, std::size_t{ 1 });
 }

If input is not in the map, then histogram[input] creates that item with a default-initialized size_t, which is zero. So you should be able to just do ++histogram[input] in either case. You are making us of this already in the operator+ case!

answered Mar 29 at 16:39
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Why not use std::variant<std::map<...>, std::vector<...>>? Also, since the ElementT is known at compile time, you could also make the choice of map or vector made at compile time. \$\endgroup\$ Commented Mar 30 at 21:57
  • \$\begingroup\$ @G.Sliepen That’s right, it can be a compile-time choice. std::variant adds complexities that are not really necessary. You save a couple of bytes in a data structure that is at least 1KB. I prefer to keep things simple. \$\endgroup\$ Commented Mar 31 at 2:36

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.