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++ and Histogram of Image using std::map in C++.

A histogram usually is defined by bins.

In this post, both histogram_normalized and histogram_with_bins template functions have been proposed.

The experimental implementation

  • histogram_normalized template function implementation

    Provide the normalized histogram of an image.

    // histogram_normalized template function implementation
    // https://codereview.stackexchange.com/q/295419/231235
    template<class ElementT = std::uint8_t, class ProbabilityType = double>
    requires (std::same_as<ElementT, std::uint8_t> or
     std::same_as<ElementT, std::uint16_t>)
    constexpr static auto histogram_normalized(const Image<ElementT>& input)
    {
     std::array<ProbabilityType, std::numeric_limits<ElementT>::max() - std::numeric_limits<ElementT>::lowest() + 1> histogram_output{};
     auto image_data = input.getImageData();
     for (std::size_t i = 0; i < image_data.size(); ++i)
     {
     histogram_output[image_data[i]] += (1.0 / static_cast<ProbabilityType>(input.count()));
     }
     return histogram_output;
    }
    // histogram_normalized template function implementation
    template<class ElementT = int, class ProbabilityType = double>
    constexpr static auto histogram_normalized(const Image<ElementT>& input)
    {
     std::map<ElementT, ProbabilityType> histogram_output{};
     auto image_data = input.getImageData();
     for (std::size_t i = 0; i < image_data.size(); ++i)
     {
     if (histogram_output.contains(image_data[i]))
     {
     histogram_output[image_data[i]] += (1.0 / static_cast<ProbabilityType>(input.count()));
     }
     else
     {
     histogram_output.emplace(image_data[i], 1.0 / static_cast<ProbabilityType>(input.count()));
     }
     }
     return histogram_output;
    }
    
  • histogram_with_bins template function implementation

    Calculate the histogram with given bins.

    // histogram_with_bins template function implementation
    template<std::size_t bins_count = 8, class ElementT = std::uint8_t>
    requires (std::same_as<ElementT, std::uint8_t> or
     std::same_as<ElementT, std::uint16_t>)
    constexpr static auto histogram_with_bins(const Image<ElementT>& input)
    {
     std::array<std::size_t, bins_count + 1> histogram_output{};
     constexpr auto max = std::numeric_limits<ElementT>::max();
     constexpr auto lowest = std::numeric_limits<ElementT>::lowest();
     auto image_data = input.getImageData();
     for (std::size_t i = 0; i < image_data.size(); ++i)
     {
     std::size_t bin_index = std::floor((static_cast<double>(image_data[i]) * static_cast<double>(bins_count)) / (static_cast<double>(max) - static_cast<double>(lowest)));
     ++histogram_output[bin_index];
     }
     return histogram_output;
    }
    

The usage of histogram_normalized template function:

/* Developed by Jimmy Hu */
#include <chrono>
#include "../base_types.h"
#include "../basic_functions.h"
#include "../image.h"
#include "../image_io.h"
#include "../image_operations.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 start1 = std::chrono::system_clock::now();
 auto histogram_result1 = TinyDIP::histogram_normalized(TinyDIP::im2uint8(TinyDIP::getVplane(hsv_image)));
 auto end1 = std::chrono::system_clock::now();
 std::chrono::duration<double> elapsed_seconds1 = end1 - start1;
 os << "elapsed time: " << elapsed_seconds1.count() << '\n';
 return histogram_result1;
}
int main()
{
 auto start = std::chrono::system_clock::now();
 std::string image_filename = "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());
 auto histogram_result1 = HistogramTest(std::execution::par, image_input);
 double sum = 0.0;
 for (std::size_t i = 0; i < histogram_result1.size(); ++i)
 {
 std::cout << i << " count: " << histogram_result1[i] << "\n";
 sum += histogram_result1[i];
 }
 std::cout << "Sum = " << sum << '\n';
 std::cout << "image_input.count = " << image_input.count() << '\n';
 auto end = std::chrono::system_clock::now();
 std::chrono::duration<double> elapsed_seconds = end - start;
 std::time_t end_time = std::chrono::system_clock::to_time_t(end);
 std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
 return EXIT_SUCCESS;
}

The output of the test code above:

Width of the input image: 454
Height of the input image: 341
Size of the input image(Byte): 464442
elapsed time: 0.0388117
0 count: 0.00141101
1 count: 4.6651e-05
2 count: 5.16749e-05
3 count: 5.3828e-05
4 count: 5.09572e-05
5 count: 3.51676e-05
6 count: 4.80864e-05
7 count: 3.73208e-05
8 count: 5.59812e-05
9 count: 4.1627e-05
10 count: 4.30624e-05
11 count: 4.1627e-05
12 count: 4.73687e-05
13 count: 5.3828e-05
14 count: 6.17228e-05
15 count: 7.03353e-05
16 count: 7.53592e-05
17 count: 9.97613e-05
18 count: 0.00021603
19 count: 0.00039761
20 count: 0.000755028
21 count: 0.00111316
22 count: 0.00172752
23 count: 0.00257872
24 count: 0.00403208
25 count: 0.00584501
26 count: 0.00756535
27 count: 0.00885292
28 count: 0.0101168
29 count: 0.0113979
30 count: 0.0121651
31 count: 0.0128333
32 count: 0.0136006
33 count: 0.0139465
34 count: 0.0144151
35 count: 0.0148472
36 count: 0.0153352
37 count: 0.0156159
38 count: 0.0161125
39 count: 0.016714
40 count: 0.0168668
41 count: 0.0170943
42 count: 0.0173491
43 count: 0.0171884
44 count: 0.0168266
45 count: 0.0163802
46 count: 0.015946
47 count: 0.015651
48 count: 0.0151702
49 count: 0.0147065
50 count: 0.014225
51 count: 0.0140986
52 count: 0.0137427
53 count: 0.0133257
54 count: 0.0130551
55 count: 0.0125857
56 count: 0.0121544
57 count: 0.0119426
58 count: 0.0113548
59 count: 0.0110075
60 count: 0.0106314
61 count: 0.0101089
62 count: 0.00984838
63 count: 0.00940268
64 count: 0.0091522
65 count: 0.00874741
66 count: 0.00859311
67 count: 0.0081711
68 count: 0.00792062
69 count: 0.00766583
70 count: 0.0073242
71 count: 0.00700554
72 count: 0.00687133
73 count: 0.00657563
74 count: 0.00649956
75 count: 0.00624405
76 count: 0.00593544
77 count: 0.00581056
78 count: 0.00550553
79 count: 0.00543663
80 count: 0.00528663
81 count: 0.00521199
82 count: 0.00503184
83 count: 0.0048029
84 count: 0.00468878
85 count: 0.00463567
86 count: 0.00430337
87 count: 0.0041505
88 count: 0.00413686
89 count: 0.00398615
90 count: 0.00388495
91 count: 0.00381102
92 count: 0.00363806
93 count: 0.00360361
94 count: 0.0036072
95 count: 0.00345648
96 count: 0.00343638
97 count: 0.00327346
98 count: 0.00324978
99 count: 0.00308327
100 count: 0.00309188
101 count: 0.00309762
102 count: 0.00295911
103 count: 0.00288734
104 count: 0.00276317
105 count: 0.00277968
106 count: 0.00267777
107 count: 0.00268207
108 count: 0.00264475
109 count: 0.00255934
110 count: 0.00251556
111 count: 0.0024847
112 count: 0.002428
113 count: 0.00240432
114 count: 0.0023426
115 count: 0.00229953
116 count: 0.00230815
117 count: 0.00228733
118 count: 0.0022902
119 count: 0.00227011
120 count: 0.0020835
121 count: 0.00218183
122 count: 0.00212154
123 count: 0.00208063
124 count: 0.00201102
125 count: 0.00209283
126 count: 0.00203326
127 count: 0.00197011
128 count: 0.00209427
129 count: 0.00204044
130 count: 0.00200599
131 count: 0.00195073
132 count: 0.00197944
133 count: 0.00199594
134 count: 0.00202465
135 count: 0.00197728
136 count: 0.00198302
137 count: 0.0019658
138 count: 0.00198805
139 count: 0.0019414
140 count: 0.00201819
141 count: 0.00196149
142 count: 0.00198518
143 count: 0.00200384
144 count: 0.0020024
145 count: 0.00201891
146 count: 0.00191915
147 count: 0.00190551
148 count: 0.00182585
149 count: 0.00183877
150 count: 0.00182441
151 count: 0.00191484
152 count: 0.00185599
153 count: 0.00180934
154 count: 0.00181221
155 count: 0.00179211
156 count: 0.00179642
157 count: 0.0018036
158 count: 0.00180647
159 count: 0.0017268
160 count: 0.00181293
161 count: 0.00184523
162 count: 0.00181652
163 count: 0.00179714
164 count: 0.00182011
165 count: 0.00176125
166 count: 0.00181508
167 count: 0.00190264
168 count: 0.0018158
169 count: 0.0018079
170 count: 0.00181006
171 count: 0.00186604
172 count: 0.00187609
173 count: 0.00186819
174 count: 0.00197226
175 count: 0.0019457
176 count: 0.00199738
177 count: 0.00206484
178 count: 0.00209642
179 count: 0.00208781
180 count: 0.00201963
181 count: 0.00197154
182 count: 0.00190049
183 count: 0.0018768
184 count: 0.00180144
185 count: 0.00181293
186 count: 0.00169594
187 count: 0.00163924
188 count: 0.00146125
189 count: 0.0014512
190 count: 0.00136939
191 count: 0.00137226
192 count: 0.001378
193 count: 0.00130479
194 count: 0.00136077
195 count: 0.00141891
196 count: 0.0014469
197 count: 0.00145695
198 count: 0.00143829
199 count: 0.00133135
200 count: 0.00119139
201 count: 0.0010658
202 count: 0.0010012
203 count: 0.000943067
204 count: 0.00091723
205 count: 0.00076364
206 count: 0.000645219
207 count: 0.000610051
208 count: 0.000607898
209 count: 0.000591391
210 count: 0.000598568
211 count: 0.000601439
212 count: 0.000602156
213 count: 0.00059785
214 count: 0.000655267
215 count: 0.000605745
216 count: 0.00057919
217 count: 0.000593544
218 count: 0.000600721
219 count: 0.00060718
220 count: 0.000638042
221 count: 0.000619381
222 count: 0.000625123
223 count: 0.000668185
224 count: 0.000652396
225 count: 0.000663161
226 count: 0.000681822
227 count: 0.000630147
228 count: 0.000642348
229 count: 0.00059785
230 count: 0.000652396
231 count: 0.000688281
232 count: 0.000624405
233 count: 0.000676798
234 count: 0.000656702
235 count: 0.000624405
236 count: 0.000643783
237 count: 0.000707659
238 count: 0.000663879
239 count: 0.00071986
240 count: 0.000716989
241 count: 0.00072919
242 count: 0.00071986
243 count: 0.000740674
244 count: 0.000765794
245 count: 0.000762205
246 count: 0.000823928
247 count: 0.000852636
248 count: 0.000940914
249 count: 0.000998331
250 count: 0.00109235
251 count: 0.00122297
252 count: 0.00151723
253 count: 0.00198518
254 count: 0.00309691
255 count: 0.0284664
Sum = 1
image_input.count = 1393326
Computation finished at Thu Feb 27 14:00:56 2025
elapsed time: 2.81971

The usage of histogram_with_bins template function:

/* Developed by Jimmy Hu */
#include <chrono>
#include "../base_types.h"
#include "../basic_functions.h"
#include "../image.h"
#include "../image_io.h"
#include "../image_operations.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 start1 = std::chrono::system_clock::now();
 auto histogram_result1 = TinyDIP::histogram_with_bins(TinyDIP::im2uint8(TinyDIP::getVplane(hsv_image)));
 auto end1 = std::chrono::system_clock::now();
 std::chrono::duration<double> elapsed_seconds1 = end1 - start1;
 os << "elapsed time: " << elapsed_seconds1.count() << '\n';
 return histogram_result1;
}
int main()
{
 auto start = std::chrono::system_clock::now();
 std::string image_filename = "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());
 auto histogram_result1 = HistogramTest(std::execution::par, image_input);
 std::size_t sum = 0;
 for (std::size_t i = 0; i < histogram_result1.size(); ++i)
 {
 std::cout << "Bin index = " << i << " count: " << histogram_result1[i] << "\n";
 sum += histogram_result1[i];
 }
 std::cout << "Sum = " << sum << '\n';
 std::cout << "image_input.count = " << image_input.count() << '\n';
 auto end = std::chrono::system_clock::now();
 std::chrono::duration<double> elapsed_seconds = end - start;
 std::time_t end_time = std::chrono::system_clock::to_time_t(end);
 std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
 return EXIT_SUCCESS;
}

The output of the test code above:

Width of the input image: 454
Height of the input image: 341
Size of the input image(Byte): 464442
elapsed time: 0.0248806
Bin index = 0 count: 114144
Bin index = 1 count: 628738
Bin index = 2 count: 253160
Bin index = 3 count: 113404
Bin index = 4 count: 85785
Bin index = 5 count: 81024
Bin index = 6 count: 39104
Bin index = 7 count: 38304
Bin index = 8 count: 39663
Sum = 1393326
image_input.count = 1393326
Computation finished at Thu Feb 27 12:22:25 2025
elapsed time: 0.635443

TinyDIP on GitHub

All suggestions are welcome.

The summary information:

asked Feb 27 at 6:06
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

You're still using std::numeric_limits<ElementT>::lowest() for the uint8 and uint16 case, which is always 0. I just find this confusing, because for the indexing you don't take this lowest() into account. So I would write

std::array<ProbabilityType, std::numeric_limits<ElementT>::max() + 1> histogram_output{};

In terms of API, I would write a normalize_histogram() instead, taking a histogram as input. You can then input a histogram computed in any way you like (with or without bins), and just divide each bin with the sum of bin values. You'll save on code duplication this way. Or, if you really like to have a histogram_normalized(), write it in terms of your regular histogram() function and this normalize_histogram().

You've limited histogram_with_bins() to uint8 and uint16 images, whereas such a function is most useful for larger integer or floating-point images, where you cannot define one bin for every possible input value.

The bin computation is also wrong, but it works because lowest is always 0 in the cases you allow:

constexpr auto max = std::numeric_limits<ElementT>::max();
constexpr auto lowest = std::numeric_limits<ElementT>::lowest();
// ...
std::floor((static_cast<double>(image_data[i]) * static_cast<double>(bins_count)) / (static_cast<double>(max) - static_cast<double>(lowest)));

Say image_data[i] == max == 50, lowest == 30, and bin_counts == 100. We get the index floor((50 * 100) / (50 - 30)) = 5000 / 20 = 250 > 100. You'll be indexing out of bounds any time lowest is not 0.

If you translate this function for signed types and floating-point types, you should do something like this:

const auto max = std::max_element(image_data.begin(), image_data.end());
const auto lowest = std::min_element(image_data.begin(), image_data.end());
const auto offset = lowest;
const auto scale = static_cast<double>(bins_count) / (max - lowest + 1);
// ...
std::floor((static_cast<double>(image_data[i]) - offset) * scale);

Why do you create an array with bins_count + 1 bins? That doesn't seem to make much sense to me, if I request 100 bins, I don't want 101.

histogram_with_bins() needs to output also what the bins are (so at least lowest, and bin_size), but you could also output an array with all the bin centers or all the bin edges, for example.

A bit more complex, but also consider allowing the user to choose a minimum and maximum for their histogram. Values outside of this range would then either be added to the first or last bin, or ignored (could again be a choice for the user).

answered Feb 28 at 21:45
\$\endgroup\$
2
  • \$\begingroup\$ > Why do you create an array with bins_count + 1 bins? Let's consider the case which ElementT is std::uint8_t, if the value image_data[i] is 255, std::floor((255.0 * 8.0) / (255.0 - 0.0)) = 8 not in the range 0~7. The latest additional bin count is for the maximum value. \$\endgroup\$ Commented Mar 3 at 6:14
  • 1
    \$\begingroup\$ @JimmyHu But then you have to fix your scaling so that 255 ends up in bin 7. If the user asks for 8 bins, you can't give them 9. Maybe divide by max - lowest + 1. \$\endgroup\$ Commented Mar 3 at 7:48

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.