diff --git a/example/Jamfile b/example/Jamfile index 02bb33163c..48b9e51aef 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -34,6 +34,7 @@ local sources = resize.cpp sobel_scharr.cpp threshold.cpp + tutorial_histogram.cpp x_gradient.cpp ; diff --git a/example/histogram.cpp b/example/histogram.cpp index b1b4896429..229b8d9b69 100644 --- a/example/histogram.cpp +++ b/example/histogram.cpp @@ -1,49 +1,52 @@ // -// Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2020 Debabrata Mandal // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#include -#include -#include -#include +#include +#include +#include -// Example file to demonstrate a way to compute histogram +#include using namespace boost::gil; -template -void gray_image_hist(GrayView const& img_view, R& hist) -{ - for (auto it = img_view.begin(); it != img_view.end(); ++it) - ++hist[*it]; - - // Alternatively, prefer the algorithm with lambda - // for_each_pixel(img_view, [&hist](gray8_pixel_t const& pixel) { - // ++hist[pixel]; - // }); -} - -template -void get_hist(const V& img_view, R& hist) { - gray_image_hist(color_converted_view(img_view), hist); -} - -int main() { - rgb8_image_t img; - read_image("test.jpg", img, jpeg_tag()); +/* +This file explains how to use the histogram class and some of its features +that can be applied for a variety of tasks. +*/ - int histogram[256]; - std::fill(histogram,histogram + 256, 0); - get_hist(const_view(img), histogram); - - std::fstream histo_file("out-histogram.txt", std::ios::out); - for(std::size_t ii = 0; ii < 256; ++ii) - histo_file << histogram[ii] << std::endl; - histo_file.close(); +int main() +{ + // Create a histogram class. Use uint or unsigned short as the default axes type in most cases. + histogram h; + + // Fill histogram with GIL images (of any color space) + gray8_image_t g; + read_image("test_adaptive.png", g, png_tag{}); + + fill_histogram + ( + view(g), // Input image view + h, // Histogram to be filled + 1, // Histogram bin widths + false, // Specify whether to accumulate over the values already present in h (default = false) + true, // Specify whether to have a sparse or continuous histogram (default = true) + false, // Specify if image mask is to be specified + {{}}, // Mask as a 2D vector. Used only if prev argument specified + {0}, // Lower limit on the values in histogram (default numeric_limit::min() on axes) + {255}, // Upper limit on the values in histogram (default numeric_limit::max() on axes) + true // Use specified limits if this is true (default is false) + ); + + // Normalize the histogram + h.normalize(); + + // Get a cumulative histogram from the histogram + auto h2 = cumulative_histogram(h); return 0; } diff --git a/example/tutorial_histogram.cpp b/example/tutorial_histogram.cpp new file mode 100644 index 0000000000..b1b4896429 --- /dev/null +++ b/example/tutorial_histogram.cpp @@ -0,0 +1,49 @@ +// +// Copyright 2005-2007 Adobe Systems Incorporated +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +#include +#include + +#include +#include + +// Example file to demonstrate a way to compute histogram + +using namespace boost::gil; + +template +void gray_image_hist(GrayView const& img_view, R& hist) +{ + for (auto it = img_view.begin(); it != img_view.end(); ++it) + ++hist[*it]; + + // Alternatively, prefer the algorithm with lambda + // for_each_pixel(img_view, [&hist](gray8_pixel_t const& pixel) { + // ++hist[pixel]; + // }); +} + +template +void get_hist(const V& img_view, R& hist) { + gray_image_hist(color_converted_view(img_view), hist); +} + +int main() { + rgb8_image_t img; + read_image("test.jpg", img, jpeg_tag()); + + int histogram[256]; + std::fill(histogram,histogram + 256, 0); + get_hist(const_view(img), histogram); + + std::fstream histo_file("out-histogram.txt", std::ios::out); + for(std::size_t ii = 0; ii < 256; ++ii) + histo_file << histogram[ii] << std::endl; + histo_file.close(); + + return 0; +} diff --git a/include/boost/gil.hpp b/include/boost/gil.hpp index 5fc7d15819..b5db505d9b 100644 --- a/include/boost/gil.hpp +++ b/include/boost/gil.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/gil/extension/histogram/std.hpp b/include/boost/gil/extension/histogram/std.hpp new file mode 100644 index 0000000000..58dcae80a9 --- /dev/null +++ b/include/boost/gil/extension/histogram/std.hpp @@ -0,0 +1,172 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#ifndef BOOST_GIL_EXTENSION_HISTOGRAM_STL_HISTOGRAM_HPP +#define BOOST_GIL_EXTENSION_HISTOGRAM_STL_HISTOGRAM_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace boost { namespace gil { + +////////////////////////////////////////////////////////// +/// Histogram extension for STL container +////////////////////////////////////////////////////////// +/// \defgroup Histogram - STL Containers +/// \brief Collection of functions to provide histogram support in GIL using Standard +/// Template Library Containers +/// The conversion from Boost.GIL images to compatible histograms are provided. The supported +/// container types would be std::vector, std::array, std::map. +/// +/// Some general constraints on STL extension:- +/// 1. Supports only 1D histogram. +/// 2. Cannot use signed images with compatible random access containers. +/// 3. Automatic resize of std::array in case of shortage of bins, to ensure +/// correctness comes before performance. +/// 4. Container key type (if exists) has to be one of std::integral types to be +/// GIL compatible. +/// 5. Container value type has to be of std::arithmetic types. +/// + +/// +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::vector of fill_histogram +/// +template +void fill_histogram(SrcView const& srcview, std::vector& histogram, bool accumulate = false) +{ + gil_function_requires>(); + static_assert(std::is_arithmetic::value, "Improper container type for images."); + static_assert( + std::is_unsigned::type>::value, + "Improper container type for signed images."); + + using channel_t = typename channel_type::type; + using pixel_t = pixel; + + if (!accumulate) + histogram.clear(); + histogram.resize(std::numeric_limits::max() + 1); + + for_each_pixel(color_converted_view(srcview), [&](pixel_t const& p) { + ++histogram[static_cast(p)]; + }); +} + +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::array of fill_histogram +/// +template +void fill_histogram(SrcView const& srcview, std::array& histogram, bool accumulate = false) +{ + gil_function_requires>(); + static_assert(std::is_arithmetic::value && N > 0, "Improper container type for images."); + static_assert( + std::is_unsigned::type>::value, + "Improper container type for signed images."); + + using channel_t = typename channel_type::type; + using pixel_t = pixel; + + const size_t pixel_max = std::numeric_limits::max(); + const float scale = (histogram.size() - 1.0f) / pixel_max; + + if (!accumulate) + std::fill(std::begin(histogram), std::end(histogram), 0); + + for_each_pixel(color_converted_view(srcview), [&](pixel_t const& p) { + ++histogram[static_cast(p * scale)]; + }); +} + +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::map of fill_histogram +/// +template +void fill_histogram(SrcView const& srcview, std::map& histogram, bool accumulate = false) +{ + gil_function_requires>(); + static_assert( + std::is_arithmetic::value && std::is_integral::value, + "Improper container type for images."); + + using channel_t = typename channel_type::type; + using pixel_t = pixel; + + if (!accumulate) + histogram.clear(); + + for_each_pixel(color_converted_view(srcview), [&](pixel_t const& p) { + ++histogram[static_cast(p)]; + }); +} + +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::vector of cumulative_histogram +/// +template +std::vector cumulative_histogram(std::vector& hist) +{ + std::vector cumulative_hist(hist.size()); + static_assert(std::is_arithmetic::value, "Improper container type for images."); + T cumulative_counter = 0; + for (std::size_t i = 0; i < hist.size(); i++) + { + cumulative_counter += hist[i]; + cumulative_hist[i] = cumulative_counter; + } + return cumulative_hist; +} + +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::array of cumulative_histogram +/// +template +std::array cumulative_histogram(std::array& histogram) +{ + std::array cumulative_hist; + static_assert(std::is_arithmetic::value && N > 0, "Improper container type for images."); + T cumulative_counter = 0; + for (std::size_t i = 0; i < N; i++) + { + cumulative_counter += histogram[i]; + cumulative_hist[i] = cumulative_counter; + } + return cumulative_hist; +} + +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::map of cumulative_histogram +/// +template +std::map cumulative_histogram(std::map& histogram) +{ + std::map cumulative_hist; + static_assert( + std::is_arithmetic::value && std::is_integral::value, + "Improper container type for images."); + T2 cumulative_counter = 0; + for (auto const& it : histogram) + { + cumulative_counter += it.second; + cumulative_hist[it.first] = cumulative_counter; + } + return cumulative_hist; +} + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/histogram.hpp b/include/boost/gil/histogram.hpp new file mode 100644 index 0000000000..bdbf9a6d7b --- /dev/null +++ b/include/boost/gil/histogram.hpp @@ -0,0 +1,717 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#ifndef BOOST_GIL_HISTOGRAM_HPP +#define BOOST_GIL_HISTOGRAM_HPP + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace boost { namespace gil { + +////////////////////////////////////////////////////////// +/// Histogram +////////////////////////////////////////////////////////// +/// \defgroup Histogram Histogram +/// \brief Contains description of the boost.gil.histogram class, extensions provided in place +/// of the default class, algorithms over the histogram class (both extensions and the +/// default class) +/// + +namespace detail { + +/// \defgroup Histogram-Helpers Histogram-Helpers +/// \brief Helper implementations supporting the histogram class. + +/// \ingroup Histogram-Helpers +/// +template +inline typename std::enable_if::type + hash_tuple_impl(std::size_t&, std::tuple const&) +{ +} + +/// \ingroup Histogram-Helpers +/// +template +inline typename std::enable_if::type + hash_tuple_impl(std::size_t& seed, std::tuple const& t) +{ + boost::hash_combine(seed, std::get(t)); + hash_tuple_impl(seed, t); +} + +/// \ingroup Histogram-Helpers +/// \brief Functor provided for the hashing of tuples. +/// The following approach makes use hash_combine from +/// boost::container_hash. Although there is a direct hashing +/// available for tuples, this approach will ease adopting in +/// future to a std::hash_combine. In case std::hash extends +/// support to tuples this functor as well as the helper +/// implementation hash_tuple_impl can be removed. +/// +template +struct hash_tuple +{ + std::size_t operator()(std::tuple const& t) const + { + std::size_t seed = 0; + hash_tuple_impl<0>(seed, t); + return seed; + } +}; + +/// \ingroup Histogram-Helpers +/// \todo With C++14 and using auto we don't need the decltype anymore +/// +template +auto pixel_to_tuple(Pixel const& p, boost::mp11::index_sequence) + -> decltype(std::make_tuple(p[I]...)) +{ + return std::make_tuple(p[I]...); +} + +/// \ingroup Histogram-Helpers +/// \todo With C++14 and using auto we don't need the decltype anymore +/// +template +auto tuple_to_tuple(Tuple const& t, boost::mp11::index_sequence) + -> decltype(std::make_tuple(std::get(t)...)) +{ + return std::make_tuple(std::get(t)...); +} + +/// \ingroup Histogram-Helpers +/// +template +bool tuple_compare(Tuple const& t1, Tuple const& t2, boost::mp11::index_sequence) +{ + std::array::value> comp_list; + comp_list = {std::get(t1) <= std::get(t2)...}; + bool comp = true; + for (std::size_t i = 0; i < comp_list.size(); i++) + { + comp = comp & comp_list[i]; + } + return comp; +} + +/// \ingroup Histogram-Helpers +/// \brief Compares 2 tuples and outputs t1 <= t2 +/// Comparison is not in a lexicographic manner but on every element of the tuple hence +/// (2, 2) > (1, 3) evaluates to false +/// +template +bool tuple_compare(Tuple const& t1, Tuple const& t2) +{ + std::size_t const tuple_size = std::tuple_size::value; + auto index_list = boost::mp11::make_index_sequence{}; + return tuple_compare(t1, t2, index_list); +} + +/// \ingroup Histogram-Helpers +/// \brief Provides equivalent of std::numeric_limits for type std::tuple +/// tuple_limit gets called with only tuples having integral elements +/// +template +struct tuple_limit +{ + static constexpr Tuple min() + { + return min_impl(boost::mp11::make_index_sequence::value>{}); + } + static constexpr Tuple max() + { + return max_impl(boost::mp11::make_index_sequence::value>{}); + } + +private: + template + static constexpr Tuple min_impl(boost::mp11::index_sequence) + { + return std::make_tuple( + std::numeric_limits::type>::min()...); + } + + template + static constexpr Tuple max_impl(boost::mp11::index_sequence) + { + return std::make_tuple( + std::numeric_limits::type>::max()...); + } +}; + +/// \ingroup Histogram-Helpers +/// \brief Filler is used to fill the histogram class with all values between a specified range +/// This functor is used when sparsefill is false, since all the keys need to be present +/// in that case. +/// Currently on 1D implementation is available, extend by adding specialization for 2D +/// and higher dimensional cases. +/// +template +struct filler +{ + template + void operator()(Container&, Tuple&, Tuple&, std::size_t) + { + } +}; + +/// \ingroup Histogram-Helpers +/// \brief Specialisation for 1D histogram. +template <> +struct filler<1> +{ + template + void operator()(Container& hist, Tuple& lower, Tuple& upper, std::size_t bin_width = 1) + { + for (auto i = std::get<0>(lower); std::get<0>(upper) - i >= bin_width; i += bin_width) + { + hist(i / bin_width) = 0; + } + hist(std::get<0>(upper) / bin_width) = 0; + } +}; + +} //namespace detail + +/// +/// \class boost::gil::histogram +/// \ingroup Histogram +/// \brief Default histogram class provided by boost::gil. +/// +/// The class inherits over the std::unordered_map provided by STL. A complete example/tutorial +/// of how to use the class resides in the docs. +/// Simple calling syntax for a 3D dimensional histogram : +/// \code +/// histogram h; +/// h(1, 1, 1) = 0; +/// \endcode +/// This is just a starter to what all can be achieved with it, refer to the docs for the +/// full demo. +/// +template +class histogram : public std::unordered_map, double, detail::hash_tuple> +{ + using base_t = std::unordered_map, double, detail::hash_tuple>; + using bin_t = boost::mp11::mp_list; + using key_t = typename base_t::key_type; + using mapped_t = typename base_t::mapped_type; + using value_t = typename base_t::value_type; + +public: + histogram() = default; + + /// \brief Returns the number of dimensions(axes) the class supports. + static constexpr std::size_t dimension() + { + return std::tuple_size::value; + } + + /// \brief Returns bin value corresponding to specified tuple + mapped_t& operator()(T... indices) + { + auto key = std::make_tuple(indices...); + std::size_t const index_dimension = std::tuple_size>::value; + std::size_t const histogram_dimension = dimension(); + static_assert(histogram_dimension == index_dimension, "Dimensions do not match."); + + return base_t::operator[](key); + } + + /// \brief Checks if 2 histograms are equal. Ignores type, and checks if + /// the keys (after type casting) match. + template + bool equals(OtherType const& otherhist) const + { + bool check = (dimension() == otherhist.dimension()); + + using other_value_t = typename OtherType::value_type; + std::for_each(otherhist.begin(), otherhist.end(), [&](other_value_t const& v) { + key_t key = key_from_tuple(v.first); + if (base_t::find(key) != base_t::end()) + { + check = check & (base_t::at(key) == otherhist.at(v.first)); + } + else + { + check = false; + } + }); + return check; + } + + /// \brief Checks if the histogram class is compatible to be used with + /// a GIL image type + static constexpr bool is_pixel_compatible() + { + using bin_types = boost::mp11::mp_list; + return boost::mp11::mp_all_of::value; + } + + /// \brief Checks if the histogram class is compatible to be used with + /// the specified tuple type + template + bool is_tuple_compatible(Tuple const&) + { + std::size_t const tuple_size = std::tuple_size::value; + std::size_t const histogram_size = dimension(); + // TODO : Explore consequence of using if-constexpr + using sequence_type = typename std::conditional + < + tuple_size >= histogram_size, + boost::mp11::make_index_sequence, + boost::mp11::make_index_sequence + >::type; + + if (is_tuple_size_compatible()) + return is_tuple_type_compatible(sequence_type{}); + else + return false; + } + + /// \brief Returns a key compatible to be used as the histogram key + /// from the input tuple + template + key_t key_from_tuple(Tuple const& t) const + { + using index_list = boost::mp11::mp_list_c; + std::size_t const index_list_size = boost::mp11::mp_size::value; + std::size_t const tuple_size = std::tuple_size::value; + std::size_t const histogram_dimension = dimension(); + + static_assert( + ((index_list_size != 0 && index_list_size == histogram_dimension) || + (tuple_size == histogram_dimension)), + "Tuple and histogram key of different sizes"); + + using new_index_list = typename std::conditional + < + index_list_size == 0, + boost::mp11::mp_list_c, + index_list + >::type; + + std::size_t const min = + boost::mp11::mp_min_element::value; + + std::size_t const max = + boost::mp11::mp_max_element::value; + + static_assert((0 <= min && max < tuple_size) || index_list_size == 0, "Index out of Range"); + + using seq1 = boost::mp11::make_index_sequence; + using seq2 = boost::mp11::index_sequence; + // TODO : Explore consequence of using if-constexpr + using sequence_type = typename std::conditional::type; + + auto key = detail::tuple_to_tuple(t, sequence_type{}); + static_assert( + is_tuple_type_compatible(seq1{}), + "Tuple type and histogram type not compatible."); + + return make_histogram_key(key, seq1{}); + } + + /// \brief Returns a histogram compatible key from the input pixel which + /// can be directly used + template + key_t key_from_pixel(Pixel const& p) const + { + using index_list = boost::mp11::mp_list_c; + std::size_t const index_list_size = boost::mp11::mp_size::value; + std::size_t const pixel_dimension = num_channels::value; + std::size_t const histogram_dimension = dimension(); + + static_assert( + ((index_list_size != 0 && index_list_size == histogram_dimension) || + (index_list_size == 0 && pixel_dimension == histogram_dimension)) && + is_pixel_compatible(), + "Pixels and histogram key are not compatible."); + + using new_index_list = typename std::conditional + < + index_list_size == 0, + boost::mp11::mp_list_c, + index_list + >::type; + + std::size_t const min = + boost::mp11::mp_min_element::value; + + std::size_t const max = + boost::mp11::mp_max_element::value; + + static_assert( + (0 <= min && max < pixel_dimension) || index_list_size == 0, "Index out of Range"); + + using seq1 = boost::mp11::make_index_sequence; + using seq2 = boost::mp11::index_sequence; + using sequence_type = typename std::conditional::type; + + auto key = detail::pixel_to_tuple(p, sequence_type{}); + return make_histogram_key(key, seq1{}); + } + + /// \brief Return nearest smaller key to specified histogram key + key_t nearest_key(key_t const& k) const + { + using check_list = boost::mp11::mp_list...>; + static_assert( + boost::mp11::mp_all_of::value, + "Keys are not comparable."); + auto nearest_k = k; + if (base_t::find(k) != base_t::end()) + { + return nearest_k; + } + else + { + bool once = true; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + if (v.first <= k) + { + if (once) + { + once = !once; + nearest_k = v.first; + } + else if (nearest_k < v.first) + nearest_k = v.first; + } + }); + return nearest_k; + } + } + + /// \brief Fills the histogram with the input image view + template + void fill( + SrcView const& srcview, + std::size_t bin_width = 1, + bool applymask = false, + std::vector> mask = {}, + key_t lower = key_t(), + key_t upper = key_t(), + bool setlimits = false) + { + gil_function_requires>(); + using channel_t = typename channel_type::type; + + for (std::ptrdiff_t src_y = 0; src_y < srcview.height(); ++src_y) + { + auto src_it = srcview.row_begin(src_y); + for (std::ptrdiff_t src_x = 0; src_x < srcview.width(); ++src_x) + { + if (applymask && !mask[src_y][src_x]) + continue; + auto scaled_px = src_it[src_x]; + static_for_each(scaled_px, [&](channel_t& ch) { + ch = ch / bin_width; + }); + auto key = key_from_pixel(scaled_px); + if (!setlimits || + (detail::tuple_compare(lower, key) && detail::tuple_compare(key, upper))) + base_t::operator[](key)++; + } + } + } + + /// \brief Can return a subset or a mask over the current histogram + template + histogram sub_histogram(Tuple const& t1, Tuple const& t2) + { + using index_list = boost::mp11::mp_list_c; + std::size_t const index_list_size = boost::mp11::mp_size::value; + std::size_t const histogram_dimension = dimension(); + + std::size_t const min = + boost::mp11::mp_min_element::value; + + std::size_t const max = + boost::mp11::mp_max_element::value; + + static_assert( + (0 <= min && max < histogram_dimension) && index_list_size < histogram_dimension, + "Index out of Range"); + + using seq1 = boost::mp11::make_index_sequence; + using seq2 = boost::mp11::index_sequence; + + static_assert( + is_tuple_type_compatible(seq1{}), + "Tuple type and histogram type not compatible."); + + auto low = make_histogram_key(t1, seq1{}); + auto low_key = detail::tuple_to_tuple(low, seq2{}); + auto high = make_histogram_key(t2, seq1{}); + auto high_key = detail::tuple_to_tuple(high, seq2{}); + + histogram sub_h; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& k) { + auto tmp_key = detail::tuple_to_tuple(k.first, seq2{}); + if (low_key <= tmp_key && tmp_key <= high_key) + sub_h[k.first] += base_t::operator[](k.first); + }); + return sub_h; + } + + /// \brief Returns a sub-histogram over specified axes + template + histogram>...> sub_histogram() + { + using index_list = boost::mp11::mp_list_c; + std::size_t const index_list_size = boost::mp11::mp_size::value; + std::size_t const histogram_dimension = dimension(); + + std::size_t const min = + boost::mp11::mp_min_element::value; + + std::size_t const max = + boost::mp11::mp_max_element::value; + + static_assert( + (0 <= min && max < histogram_dimension) && index_list_size < histogram_dimension, + "Index out of Range"); + + histogram>...> sub_h; + + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + auto sub_key = + detail::tuple_to_tuple(v.first, boost::mp11::index_sequence{}); + sub_h[sub_key] += base_t::operator[](v.first); + }); + return sub_h; + } + + /// \brief Normalize this histogram class + void normalize() + { + double sum = 0.0; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + sum += v.second; + }); + // std::cout<<(long int)sum<<"asfe"; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + base_t::operator[](v.first) = v.second / sum; + }); + } + + /// \brief Return the sum count of all bins + double sum() const + { + double sum = 0.0; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + sum += v.second; + }); + return sum; + } + + /// \brief Return the minimum key in histogram + key_t min_key() const + { + key_t min_key = base_t::begin()->first; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + if (v.first < min_key) + min_key = v.first; + }); + return min_key; + } + + /// \brief Return the maximum key in histogram + key_t max_key() const + { + key_t max_key = base_t::begin()->first; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + if (v.first > max_key) + max_key = v.first; + }); + return max_key; + } + + /// \brief Return sorted keys in a vector + std::vector sorted_keys() const + { + std::vector sorted_keys; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + sorted_keys.push_back(v.first); + }); + std::sort(sorted_keys.begin(), sorted_keys.end()); + return sorted_keys; + } + +private: + template + key_t make_histogram_key(Tuple const& t, boost::mp11::index_sequence) const + { + return std::make_tuple( + static_cast>>( + std::get(t))...); + } + + template + static constexpr bool is_tuple_type_compatible(boost::mp11::index_sequence) + { + using tp = boost::mp11::mp_list + < + typename std::is_convertible + < + boost::mp11::mp_at>, + typename std::tuple_element::type + >::type... + >; + return boost::mp11::mp_all_of::value; + } + + template + static constexpr bool is_tuple_size_compatible() + { + return (std::tuple_size::value == dimension()); + } +}; + +/// +/// \fn void fill_histogram +/// \ingroup Histogram Algorithms +/// \tparam SrcView Input image view +/// \tparam Container Input histogram container +/// \brief Overload this function to provide support for boost::gil::histogram or +/// any other external histogram +/// +/// Example : +/// \code +/// histogram h; +/// fill_histogram(view(img), h); +/// \endcode +/// +template +void fill_histogram(SrcView const&, Container&); + +/// +/// \fn void fill_histogram +/// \ingroup Histogram Algorithms +/// @param srcview Input Input image view +/// @param hist Output Histogram to be filled +/// @param bin_width Input Specify the bin widths for the histogram. +/// @param accumulate Input Specify whether to accumulate over the values already present in h (default = false) +/// @param sparsaefill Input Specify whether to have a sparse or continuous histogram (default = true) +/// @param applymask Input Specify if image mask is to be specified +/// @param mask Input Mask as a 2D vector. Used only if prev argument specified +/// @param lower Input Lower limit on the values in histogram (default numeric_limit::min() on axes) +/// @param upper Input Upper limit on the values in histogram (default numeric_limit::max() on axes) +/// @param setlimits Input Use specified limits if this is true (default is false) +/// \brief Overload version of fill_histogram +/// +/// Takes a third argument to determine whether to clear container before filling. +/// For eg, when there is a need to accumulate the histograms do +/// \code +/// fill_histogram(view(img), hist, true); +/// \endcode +/// +template +void fill_histogram( + SrcView const& srcview, + histogram& hist, + std::size_t bin_width = 1, + bool accumulate = false, + bool sparsefill = true, + bool applymask = false, + std::vector> mask = {}, + typename histogram::key_type lower = + detail::tuple_limit::key_type>::min(), + typename histogram::key_type upper = + detail::tuple_limit::key_type>::max(), + bool setlimits = false) +{ + if (!accumulate) + hist.clear(); + + detail::filler::dimension()> f; + if (!sparsefill) + f(hist, lower, upper, bin_width); + + hist.template fill(srcview, bin_width, applymask, mask, lower, upper, setlimits); +} + +/// +/// \fn void cumulative_histogram(Container&) +/// \ingroup Histogram Algorithms +/// \tparam Container Input histogram container +/// \brief Optionally overload this function with any external histogram class +/// +/// Cumulative histogram is calculated over any arbitrary dimensional +/// histogram. The only tradeoff could be the runtime complexity which in +/// the worst case would be max( #pixel_values , #bins ) * #dimensions. +/// For single dimensional histograms the complexity has been brought down to +/// #bins * log( #bins ) by sorting the keys and then calculating the cumulative version. +/// +template +Container cumulative_histogram(Container const&); + +template +histogram cumulative_histogram(histogram const& hist) +{ + using check_list = boost::mp11::mp_list...>; + static_assert( + boost::mp11::mp_all_of::value, + "Cumulative histogram not possible of this type"); + + using histogram_t = histogram; + using pair_t = std::pair; + using value_t = typename histogram_t::value_type; + + histogram_t cumulative_hist; + std::size_t const dims = histogram_t::dimension(); + if (dims == 1) + { + std::vector sorted_keys(hist.size()); + std::size_t counter = 0; + std::for_each(hist.begin(), hist.end(), [&](value_t const& v) { + sorted_keys[counter++] = std::make_pair(v.first, v.second); + }); + std::sort(sorted_keys.begin(), sorted_keys.end()); + auto cumulative_counter = static_cast(0); + for (std::size_t i = 0; i < sorted_keys.size(); ++i) + { + cumulative_counter += sorted_keys[i].second; + cumulative_hist[(sorted_keys[i].first)] = cumulative_counter; + } + } + else + { + std::for_each(hist.begin(), hist.end(), [&](value_t const& v1) { + auto cumulative_counter = static_cast(0); + std::for_each(hist.begin(), hist.end(), [&](value_t const& v2) { + bool comp = detail::tuple_compare( + v2.first, v1.first, + boost::mp11::make_index_sequence{}); + if (comp) + cumulative_counter += hist.at(v2.first); + }); + cumulative_hist[v1.first] = cumulative_counter; + }); + } + return cumulative_hist; +} + +}} //namespace boost::gil + +#endif diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index ed3b36f497..6abbfdee88 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -39,3 +39,4 @@ add_subdirectory(image) add_subdirectory(image_view) add_subdirectory(algorithm) add_subdirectory(image_processing) +add_subdirectory(histogram) diff --git a/test/core/Jamfile b/test/core/Jamfile index 7ced838338..45992e30ee 100644 --- a/test/core/Jamfile +++ b/test/core/Jamfile @@ -32,3 +32,4 @@ build-project image ; build-project image_view ; build-project algorithm ; build-project image_processing ; +build-project histogram ; diff --git a/test/core/histogram/CMakeLists.txt b/test/core/histogram/CMakeLists.txt new file mode 100644 index 0000000000..42a4931cfd --- /dev/null +++ b/test/core/histogram/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright 2020 Debabrata Mandal +# +# Distributed under the Boost Software License, Version 1.0 +# See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt +# + +foreach(_name + access + constructor + cumulative + dimension + fill + hash_tuple + helpers + is_compatible + key + sub_histogram + utilities) + set(_test t_core_histogram_${_name}) + set(_target test_core_histogram_${_name}) + + add_executable(${_target} "") + target_sources(${_target} PRIVATE ${_name}.cpp) + target_link_libraries(${_target} + PRIVATE + gil_compile_options + gil_include_directories + gil_dependencies) + target_compile_definitions(${_target} PRIVATE BOOST_GIL_USE_CONCEPT_CHECK) + add_test(NAME ${_test} COMMAND ${_target}) + + unset(_name) + unset(_target) + unset(_test) +endforeach() diff --git a/test/core/histogram/Jamfile b/test/core/histogram/Jamfile new file mode 100644 index 0000000000..883fba7096 --- /dev/null +++ b/test/core/histogram/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright 2020 Debabrata Mandal +# +# Distributed under the Boost Software License, Version 1.0 +# See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt +# + +import testing ; + +compile constructor.cpp ; +compile dimension.cpp ; +run access.cpp ; +run cumulative.cpp ; +run fill.cpp ; +run hash_tuple.cpp ; +run helpers.cpp ; +run is_compatible.cpp ; +run key.cpp ; +run sub_histogram.cpp ; +run utilities.cpp ; diff --git a/test/core/histogram/access.cpp b/test/core/histogram/access.cpp new file mode 100644 index 0000000000..0c1847e042 --- /dev/null +++ b/test/core/histogram/access.cpp @@ -0,0 +1,35 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include + +#include + +#include + +namespace gil = boost::gil; + +void check_indexing_operator() +{ + gil::histogram h1; + h1(1) = 3; + BOOST_TEST(h1(1) == 3); + BOOST_TEST(h1(3) == 0); + + gil::histogram h2; + h2(1, 'a', "A") = 4; + BOOST_TEST(h2(1, 'a', "A") == 4); + BOOST_TEST(h2(1, 'a', "B") == 0); +} + +int main() { + + check_indexing_operator(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/constructor.cpp b/test/core/histogram/constructor.cpp new file mode 100644 index 0000000000..92817b8f9a --- /dev/null +++ b/test/core/histogram/constructor.cpp @@ -0,0 +1,31 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include + +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +void check_histogram_constructors() +{ + gil::histogram h1; + gil::histogram h2 = h1; + gil::histogram h3; + gil::histogram h4(h3); + + gil::histogram d1, d2 = d1, d3(d2); +} + +int main() { + + check_histogram_constructors(); + return 0; + +} diff --git a/test/core/histogram/cumulative.cpp b/test/core/histogram/cumulative.cpp new file mode 100644 index 0000000000..c06f710890 --- /dev/null +++ b/test/core/histogram/cumulative.cpp @@ -0,0 +1,54 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include + +#include + +namespace gil = boost::gil; + +void check_cumulative() +{ + gil::histogram h1; + for (int i = 0; i < 8; i++) + { + h1(i) = 1; + } + auto h2 = cumulative_histogram(h1); + bool check1 = true; + for (int i = 0; i < 8; i++) + { + if(h2(i) != i+1) + check1 = false; + } + BOOST_TEST(check1); + + gil::histogram h3; + h3(1, 3) = 1; + h3(1, 4) = 2; + h3(2, 1) = 3; + h3(2, 2) = 1; + h3(2, 5) = 2; + h3(3, 2) = 3; + h3(3, 9) = 1; + auto h4 = cumulative_histogram(h3); + BOOST_TEST(h4(1, 3) == 1); + BOOST_TEST(h4(1, 4) == 3); + BOOST_TEST(h4(2, 1) == 3); + BOOST_TEST(h4(2, 2) == 4); + BOOST_TEST(h4(2, 5) == 9); + BOOST_TEST(h4(3, 2) == 7); + BOOST_TEST(h4(3, 9) == 13); +} + +int main() { + + check_cumulative(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/dimension.cpp b/test/core/histogram/dimension.cpp new file mode 100644 index 0000000000..912f15d27f --- /dev/null +++ b/test/core/histogram/dimension.cpp @@ -0,0 +1,31 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +void check_histogram_constructors() +{ + gil::histogram h1; + gil::histogram h2 = h1; + gil::histogram h3; + gil::histogram h4(h3); + + static_assert(h1.dimension() == h2.dimension(),"Dimension mismatch"); + static_assert(h1.dimension() != h3.dimension(),"Dimension mismatch"); + static_assert(h3.dimension() == h4.dimension(),"Dimension mismatch"); +} + +int main() { + + check_histogram_constructors(); + return 0; + +} diff --git a/test/core/histogram/fill.cpp b/test/core/histogram/fill.cpp new file mode 100644 index 0000000000..ac43dc1f5a --- /dev/null +++ b/test/core/histogram/fill.cpp @@ -0,0 +1,282 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include +#include +#include +#include + +#include +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +gil::gray8_image_t img1(4, 4, gil::gray8_pixel_t(1)); +gil::gray8_view_t v1 = view(img1); + +gil::rgb8_image_t img2(4, 4, gil::rgb8_pixel_t(1)); +gil::rgb8_view_t v2 = view(img2); + +std::uint8_t sparse_matrix[] = +{ + 1, 1, 1, 1, + 3, 3, 3, 3, + 5, 5, 5, 5, + 7, 7, 7, 7 +}; + +std::uint8_t big_matrix[] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 3, 4, 5, 6, 7, 8, + 3, 4, 3, 4, 3, 4, 3, 4, + 1, 2, 3, 4, 5, 6, 7, 8, + 5, 6, 5, 6, 5, 6, 5, 6, + 1, 2, 3, 4, 5, 6, 7, 8, + 7, 8, 7, 8, 7, 8, 7, 8 +}; + +std::uint8_t big_rgb_matrix[] = +{ + 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, 9, 10, + 1, 2, 3, 2, 3, 4, 1, 2, 3, 2, 3, 4, 1, 2, 3, 2, 3, 4, 1, 2, 3, 2, 3, 4, + 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, 9, 10, + 3, 4, 5, 4, 5, 6, 3, 4, 5, 4, 5, 6, 3, 4, 5, 4, 5, 6, 3, 4, 5, 4, 5, 6, + 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, 9, 10, + 5, 6, 7, 6, 7, 8, 5, 6, 7, 6, 7, 8, 5, 6, 7, 6, 7, 8, 5, 6, 7, 6, 7, 8, + 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, 9, 10, + 7, 8, 9, 8, 9, 10, 7, 8, 9, 8, 9, 10, 7, 8, 9, 8, 9, 10, 7, 8, 9, 8, 9, 10, +}; + +std::vector> mask = +{ + {1, 0, 0, 1}, + {0, 0, 1, 1}, + {0, 1, 0, 1}, + {1, 1, 0, 0}, +}; + +gil::gray8c_view_t sparse_gray_view = gil::interleaved_view(4, 4, reinterpret_cast(sparse_matrix), 4); + +gil::gray8c_view_t big_gray_view = gil::interleaved_view(8, 8, reinterpret_cast(big_matrix), 8); + +gil::rgb8c_view_t big_rgb_view = gil::interleaved_view(8, 8, reinterpret_cast(big_rgb_matrix), 24); + +void check_histogram_fill_test1() +{ + gil::histogram h1; + + h1.fill(big_gray_view); + + bool check_gray_fill = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h1(i) != 8) + { + check_gray_fill = false; + } + } + BOOST_TEST(check_gray_fill); +} + +void check_histogram_fill_test2() +{ + gil::histogram h3; + h3.fill(big_rgb_view); + + bool check_rgb_fill = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h3(i, i+1, i+2) != 8) + { + check_rgb_fill = false; + } + } + BOOST_TEST(check_rgb_fill); +} + +void check_histogram_fill_test3() +{ + gil::histogram h2; + h2.fill<1>(big_rgb_view); + bool check_gray_fill2 = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h2(i+1) != 8) + { + check_gray_fill2 = false; + } + } + BOOST_TEST(check_gray_fill2); +} + +void check_histogram_fill_test4() +{ + gil::histogram h1; + // Check with limits + std::tuple lower{2}, higher{6}; + h1.clear(); + h1.fill(big_gray_view, 1, false, {{}}, lower, higher, true); + bool check_gray_fill = true; + check_gray_fill = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(i<2 || i>6) + { + check_gray_fill = check_gray_fill & (h1(i)==0);continue; + } + if(h1(i) != 8) + { + check_gray_fill = false; + } + } + BOOST_TEST(check_gray_fill); +} + +void check_histogram_fill_test5() +{ + gil::histogram h3; + std::tuple lower1{2,2,2}, higher1{6,6,6}; + h3.clear(); + h3.fill(big_rgb_view, 1, false, {{}}, lower1, higher1, true); + + bool check_rgb_fill = true; + check_rgb_fill = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(!(i >= 2 && (i+2) <= 6)) + { + check_rgb_fill = check_rgb_fill & (h3(i, i+1, i+2)==0);continue; + } + if(h3(i, i+1, i+2) != 8) + { + check_rgb_fill = false; + } + } + BOOST_TEST(check_rgb_fill); +} + +void check_histogram_fill_test6() +{ + gil::histogram h2; + h2.clear(); + std::tuple lower{2}, higher{6}; + h2.fill<1>(big_rgb_view, 1, false, {{}}, lower, higher, true); + bool check_gray_fill2 = true; + check_gray_fill2 = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(i+1 < 2 || i+1 > 6) + { + check_gray_fill2 = check_gray_fill2 & (h2(i+1)==0);continue; + } + if(h2(i+1) != 8) + { + check_gray_fill2 = false; + } + } + BOOST_TEST(check_gray_fill2); +} + +void check_histogram_fill_test7() +{ + //Check masking + gil::histogram h4; + std::tuple low{1}, high{8}; + gil::fill_histogram(sparse_gray_view, h4, 1, false, false, true, mask, low, high, true); + + bool check_1d = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(i%2==1) + { + check_1d = check_1d & (h4(i)==2); + } + } + BOOST_TEST(check_1d); +} + +void check_histogram_fill_algorithm() +{ + gil::histogram h1; + + gil::fill_histogram<1>(big_rgb_view, h1); + + bool check_1d = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h1(i+1) != 8) + { + check_1d = false; + } + } + BOOST_TEST(check_1d); + + gil::histogram h2; + + gil::fill_histogram<2, 1>(big_rgb_view, h2); + + bool check_2d = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h2(i+2, i+1) != 8) + { + check_2d = false; + } + } + BOOST_TEST(check_2d); + + gil::histogram h3; + + std::tuple low(1), high(8); + gil::fill_histogram(sparse_gray_view, h3, 1, false, false, false, {{}}, low, high, true); + + check_1d = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h3.find(std::tuple(i)) == h3.end()) + { + check_1d = false; + } + else + { + check_1d = check_1d & (i % 2 == 1 ? (h3(i) == 4) : (h3(i) == 0)); + } + } + BOOST_TEST(check_1d); +} + +void check_fill_bin_width() +{ + gil::histogram h1; + gil::fill_histogram(big_gray_view, h1, 2); + bool check1 = true; + for(std::size_t i = 1; i <= 3; ++i) + { + check1 = check1 & (h1(i) == 16); + } + check1 = check1 & (h1(0) == 8) & (h1(4) == 8); + BOOST_TEST(check1); +} + +int main() { + + check_histogram_fill_test1(); + check_histogram_fill_test2(); + check_histogram_fill_test3(); + check_histogram_fill_test4(); + check_histogram_fill_test5(); + check_histogram_fill_test6(); + check_histogram_fill_test7(); + check_histogram_fill_algorithm(); + check_fill_bin_width(); + + return boost::report_errors(); +} \ No newline at end of file diff --git a/test/core/histogram/hash_tuple.cpp b/test/core/histogram/hash_tuple.cpp new file mode 100644 index 0000000000..69d47d16a2 --- /dev/null +++ b/test/core/histogram/hash_tuple.cpp @@ -0,0 +1,35 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include + +#include +#include + +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +void check_detail_hash_tuple () +{ + std::tuple t(1, 1); + std::size_t seed1 = 0; + boost::hash_combine(seed1, std::get<0>(t)); + boost::hash_combine(seed1, std::get<1>(t)); + + gil::detail::hash_tuple g; + std::size_t seed2 = g(t); + BOOST_TEST(seed1 == seed2); +} + +int main() +{ + check_detail_hash_tuple(); + return boost::report_errors(); +} diff --git a/test/core/histogram/helpers.cpp b/test/core/histogram/helpers.cpp new file mode 100644 index 0000000000..38fafd9f2a --- /dev/null +++ b/test/core/histogram/helpers.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include +#include +#include + +#include +#include + +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +void check_helper_fn_pixel_to_tuple() +{ + gil::gray8_pixel_t g1(2); + auto g2 = gil::detail::pixel_to_tuple(g1, mp11::make_index_sequence<1>{}); + + bool const same_gray_type = std::is_same, decltype(g2)>::value; + BOOST_TEST(same_gray_type); + BOOST_TEST(g1[0] == std::get<0>(g2)); + + gil::rgb8_pixel_t r1(1,2,3); + auto r2 = gil::detail::pixel_to_tuple(r1, mp11::index_sequence<0, 1, 2>{}); + + bool const same_rgb_type = std::is_same, + decltype(r2)>::value; + BOOST_TEST(same_rgb_type); + BOOST_TEST(r1[0] == std::get<0>(r2) && r1[1] == std::get<1>(r2) && r1[2] == std::get<2>(r2)); + + auto r3 = gil::detail::pixel_to_tuple(r1, mp11::index_sequence<1, 2, 0>{}); + BOOST_TEST(r1[0] == std::get<2>(r3) && r1[1] == std::get<0>(r3) && r1[2] == std::get<1>(r3)); +} + +void check_helper_fn_tuple_to_tuple() +{ + std::tuple t1(1); + auto t2 = gil::detail::tuple_to_tuple(t1, mp11::make_index_sequence<1>{}); + + bool const same_gray_type = std::is_same, decltype(t2)>::value; + BOOST_TEST(same_gray_type); + BOOST_TEST(std::get<0>(t1) == std::get<0>(t2)); + + std::tuple r1(1, 2, "A"); + auto r2 = gil::detail::tuple_to_tuple(r1, mp11::index_sequence<0, 1, 2>{}); + + bool const same_rgb_type = std::is_same, + decltype(r2)>::value; + BOOST_TEST(same_rgb_type); + BOOST_TEST( std::get<0>(r1) == std::get<0>(r2) && + std::get<1>(r1) == std::get<1>(r2) && + std::get<2>(r1) == std::get<2>(r2)); + + auto r3 = gil::detail::tuple_to_tuple(r1, mp11::index_sequence<1, 2, 0>{}); + BOOST_TEST( std::get<0>(r1) == std::get<2>(r3) && + std::get<1>(r1) == std::get<0>(r3) && + std::get<2>(r1) == std::get<1>(r3)); +} + +void check_helper_tuple_limit() +{ + using type1 = std::tuple; + using type2 = std::tuple; + type1 t1_min(std::numeric_limits::min(), std::numeric_limits::min()); + type1 t1_max(std::numeric_limits::max(), std::numeric_limits::max()); + type2 t2_min(std::numeric_limits::min(), std::numeric_limits::min()); + type2 t2_max(std::numeric_limits::max(), std::numeric_limits::max()); + + BOOST_TEST(t1_min == gil::detail::tuple_limit::min()); + BOOST_TEST(t1_max == gil::detail::tuple_limit::max()); + BOOST_TEST(t2_min == gil::detail::tuple_limit::min()); + BOOST_TEST(t2_max == gil::detail::tuple_limit::max()); + +} + +void check_filler() +{ + boost::gil::histogram h; + boost::gil::detail::filler<1> f; + std::tuple l1{4}, h1{13}; + f(h, l1, h1); + + std::tuple l2{20}, h2{33}; + f(h, l2, h2); + + bool check = true; + for(int i = 0; i < 100; i++) + { + if((i >= 4 && i <= 13) || (i >= 20 && i <= 33)) + { + if(h.find(std::tuple(i))==h.end()) + check = false; + } + } + BOOST_TEST(check); +} + +int main() { + + check_helper_fn_pixel_to_tuple(); + check_helper_fn_tuple_to_tuple(); + check_helper_tuple_limit(); + check_filler(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/is_compatible.cpp b/test/core/histogram/is_compatible.cpp new file mode 100644 index 0000000000..bb150943a9 --- /dev/null +++ b/test/core/histogram/is_compatible.cpp @@ -0,0 +1,72 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include + +#include + +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + + +void check_is_pixel_compatible() +{ + gil::histogram h1; + gil::histogram h2; + gil::histogram h3; + gil::histogram h4; + + BOOST_TEST(h1.is_pixel_compatible()); + BOOST_TEST(h2.is_pixel_compatible()); + BOOST_TEST(h3.is_pixel_compatible()); + BOOST_TEST(!h4.is_pixel_compatible()); +} + +void check_is_tuple_compatible() +{ + gil::histogram h1; + gil::histogram h2; + gil::histogram h3; + gil::histogram h4; + gil::histogram h5; + + std::tuple t1; + std::tuple t2; + std::tuple t3; + std::tuple t4; + std::tuple t5; + + BOOST_TEST(h1.is_tuple_compatible(t1)); + BOOST_TEST(h1.is_tuple_compatible(t2)); + BOOST_TEST(!h1.is_tuple_compatible(t3)); + BOOST_TEST(!h1.is_tuple_compatible(t5)); + + BOOST_TEST(h2.is_tuple_compatible(t1)); + BOOST_TEST(h2.is_tuple_compatible(t2)); + BOOST_TEST(!h2.is_tuple_compatible(t3)); + + BOOST_TEST(!h3.is_tuple_compatible(t1)); + BOOST_TEST(h3.is_tuple_compatible(t3)); + BOOST_TEST(h3.is_tuple_compatible(t4)); + BOOST_TEST(!h3.is_tuple_compatible(t5)); + + BOOST_TEST(!h4.is_tuple_compatible(t1)); + BOOST_TEST(h4.is_tuple_compatible(t3)); + BOOST_TEST(h4.is_tuple_compatible(t4)); + BOOST_TEST(!h4.is_tuple_compatible(t5)); +} + +int main() { + + check_is_pixel_compatible(); + check_is_tuple_compatible(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/key.cpp b/test/core/histogram/key.cpp new file mode 100644 index 0000000000..e4e2418aef --- /dev/null +++ b/test/core/histogram/key.cpp @@ -0,0 +1,63 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include +#include +#include + +#include + +#include + +namespace gil = boost::gil; + +void check_histogram_key_from_tuple() +{ + gil::histogram h1; + std::tuple t1(1, 2); + auto t2 = h1.key_from_tuple(t1); + const bool same_type = std::is_same, decltype(t2)>::value; + + BOOST_TEST(same_type); + BOOST_TEST(std::get<0>(t2) == 1 && std::get<1>(t2) == 2); + + std::tuple t3(1, 2, 4, 2); + auto t4 = h1.key_from_tuple<0, 2>(t3); + const bool same_type1 = std::is_same, decltype(t4)>::value; + + BOOST_TEST(same_type1); + BOOST_TEST(std::get<0>(t4) == 1 && std::get<1>(t4) == 4); +} + +void check_histogram_key_from_pixel() +{ + gil::histogram h1; + gil::gray8_pixel_t g1(1); + auto t1 = h1.key_from_pixel(g1); + const bool same_type = std::is_same, decltype(t1)>::value; + + BOOST_TEST(same_type); + BOOST_TEST(std::get<0>(t1) == 1); + + gil::histogram h2; + gil::rgb8_pixel_t r1(1, 0, 3); + auto t2 = h2.key_from_pixel<0, 2>(r1); + const bool same_type1 = std::is_same, decltype(t2)>::value; + + BOOST_TEST(same_type1); + BOOST_TEST(std::get<0>(t2) == 1 && std::get<1>(t2) == 3); +} + +int main() { + + check_histogram_key_from_tuple(); + check_histogram_key_from_pixel(); + + return boost::report_errors(); +} + \ No newline at end of file diff --git a/test/core/histogram/sub_histogram.cpp b/test/core/histogram/sub_histogram.cpp new file mode 100644 index 0000000000..af618b2334 --- /dev/null +++ b/test/core/histogram/sub_histogram.cpp @@ -0,0 +1,58 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include + +#include + +#include + +namespace gil = boost::gil; + +void check_sub_histogram_without_tuple() +{ + gil::histogram h; + h(1, 1, "A", 1) = 1; + h(1, 2, "B", 1) = 1; + h(2, 1, "C", 1) = 1; + h(2, 1, "D", 1) = 1; + h(2, 3, "E", 4) = 1; + auto h1 = h.sub_histogram<0,3>(); + BOOST_TEST(h1(1, 1) == 2); + BOOST_TEST(h1(2, 1) == 2); + BOOST_TEST(h1(2, 4) == 1); + BOOST_TEST(h1(5, 5) == 0); +} + +void check_sub_histogram_with_tuple() +{ + gil::histogram h; + h(1, 1, "A", 1) = 3; + h(1, 2, "C", 1) = 1; + h(2, 1, "C", 1) = 1; + h(2, 1, "A", 1) = 1; + h(2, 3, "E", 4) = 1; + h(1, 3, "A", 1) = 2; + std::tuple t(1.0, 1000, "A", 1); + // This means 1st dimension is useless for matching. + auto h1 = h.sub_histogram<0,2,3>(t, t); + BOOST_TEST(h1(1, 1, "A", 1) == 3); + BOOST_TEST(h1(1, 2, "C", 1) == 0); + BOOST_TEST(h1(2, 1, "C", 1) == 0); + BOOST_TEST(h1(2, 1, "A", 1) == 0); + BOOST_TEST(h1(2, 1, "A", 1) == 0); + BOOST_TEST(h1(1, 3, "A", 1) == 2); +} + +int main() { + + check_sub_histogram_without_tuple(); + check_sub_histogram_with_tuple(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/utilities.cpp b/test/core/histogram/utilities.cpp new file mode 100644 index 0000000000..4f26a05b41 --- /dev/null +++ b/test/core/histogram/utilities.cpp @@ -0,0 +1,214 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include +#include +#include +#include + +#include +#include +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +std::uint8_t big_matrix[] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 3, 4, 5, 6, 7, 8, + 3, 4, 3, 4, 3, 4, 3, 4, + 1, 2, 3, 4, 5, 6, 7, 8, + 5, 6, 5, 6, 5, 6, 5, 6, + 1, 2, 3, 4, 5, 6, 7, 8, + 7, 8, 7, 8, 7, 8, 7, 8 +}; + +void check_normalize() +{ + auto epsilon = 1e-6; + // 1D histogram + double expected[64]; + gil::histogram h1; + int sum = 0; + for (std::size_t i = 0; i < 64; i++) + { + h1(i) = big_matrix[i]; + sum += big_matrix[i]; + } + for (std::size_t i = 0; i < 64; i++) + { + expected[i] = double(big_matrix[i]) / sum; + } + h1.normalize(); + + bool check = true; + for (std::size_t i = 0; i < 64; i++) + { + check = check & (abs(expected[i] - h1(i)) < epsilon); + } + BOOST_TEST(check); + + // 2D histogram + double expected2[8][8]; + gil::histogram h2; + int sum2 = 0; + for (std::size_t i = 0; i < 64; i++) + { + h2(i/8, i%8) = big_matrix[i]; + sum2 += big_matrix[i]; + } + for (std::size_t i = 0; i < 64; i++) + { + expected2[i/8][i%8] = double(big_matrix[i]) / sum2; + } + h2.normalize(); + + bool check2 = true; + for (std::size_t i = 0; i < 64; i++) + { + check2 = check2 & (abs(expected2[i/8][i%8] - h2(i/8,i%8)) < epsilon); + } + BOOST_TEST(check2); +} + +void check_nearest_key() +{ + { + gil::histogram h1; + h1(1) = 1; + h1(3) = 4; + h1(4) = 4; + h1(6) = 1; + std::tuple k1{2}, k2{3}, k3{5}; + std::tuple k1_expected{1}, k2_expected{3}, k3_expected{4}; + BOOST_TEST(k1_expected == h1.nearest_key(k1)); + BOOST_TEST(k2_expected == h1.nearest_key(k2)); + BOOST_TEST(k3_expected == h1.nearest_key(k3)); + } + + { + gil::histogram h2; + h2(1, 1) = 1; + h2(1, 4) = 1; + h2(2, 4) = 1; + h2(4, 4) = 1; + std::tuple k1(1, 1), k2(1, 3), k3(2, 1), k4(2, 7), k5(4, 4); + std::tuple k1_exp(1, 1), k2_exp(1, 1), k3_exp(1, 4), k4_exp(2, 4), k5_exp(4, 4); + BOOST_TEST(k1_exp == h2.nearest_key(k1)); + BOOST_TEST(k2_exp == h2.nearest_key(k2)); + BOOST_TEST(k3_exp == h2.nearest_key(k3)); + BOOST_TEST(k4_exp == h2.nearest_key(k4)); + BOOST_TEST(k5_exp == h2.nearest_key(k5)); + } + +} + +void check_equals() +{ + gil::histogram h, h2; + h(1) = 3; + h(4) = 1; + h(2) = 6; + h(7) = 3; + h(9) = 7; + h2 = h; + BOOST_TEST(h2.equals(h)); + + gil::histogram h3; + h3(1) = 3; + h3(4) = 1; + h3(2) = 6; + h3(7) = 3; + h3(9) = 7; + BOOST_TEST(h3.equals(h)); +} + +void check_sum() +{ + gil::histogram h; + h(1) = 3; + h(4) = 1; + h(2) = 6; + h(7) = 3; + h(9) = 7; + auto sm = h.sum(); + BOOST_TEST(sm == 20); +} + +void check_max_key() +{ + gil::histogram h; + h(1) = 3; + h(4) = 1; + h(2) = 6; + h(7) = 3; + h(9) = 7; + BOOST_TEST(std::get<0>(h.max_key()) == 9); + + gil::histogram h2; + h2(1, 4) = 3; + h2(4, 2) = 1; + h2(2, 5) = 6; + h2(7, 4) = 3; + h2(9, 1) = 7; + h2(9, 3) = 7; + BOOST_TEST(std::get<0>(h2.max_key()) == 9 && std::get<1>(h2.max_key()) == 3); +} + +void check_min_key() +{ + gil::histogram h; + h(1) = 3; + h(4) = 1; + h(2) = 6; + h(7) = 3; + h(9) = 7; + BOOST_TEST(std::get<0>(h.min_key()) == 1); + + gil::histogram h2; + h2(1, 4) = 3; + h2(4, 2) = 1; + h2(2, 5) = 6; + h2(7, 4) = 3; + h2(9, 1) = 7; + h2(9, 3) = 7; + BOOST_TEST(std::get<0>(h2.min_key()) == 1 && std::get<1>(h2.min_key()) == 4); +} + +void check_sorted_keys() +{ + gil::histogram h; + h(1) = 3; + h(4) = 1; + h(2) = 6; + h(7) = 3; + h(9) = 7; + + std::vector> v; + v.push_back(std::tuple(1)); + v.push_back(std::tuple(2)); + v.push_back(std::tuple(4)); + v.push_back(std::tuple(7)); + v.push_back(std::tuple(9)); + BOOST_TEST(v == h.sorted_keys()); +} + +int main() { + + check_normalize(); + check_nearest_key(); + check_equals(); + check_max_key(); + check_min_key(); + check_sum(); + check_sorted_keys(); + + return boost::report_errors(); +} diff --git a/test/extension/CMakeLists.txt b/test/extension/CMakeLists.txt index b642d2b296..9f7883066f 100644 --- a/test/extension/CMakeLists.txt +++ b/test/extension/CMakeLists.txt @@ -9,6 +9,10 @@ if(BOOST_GIL_ENABLE_EXT_DYNAMIC_IMAGE) add_subdirectory(dynamic_image) endif() +if(BOOST_GIL_ENABLE_EXT_HISTOGRAM) + add_subdirectory(histogram) +endif() + if(BOOST_GIL_ENABLE_EXT_NUMERIC) add_subdirectory(numeric) endif() diff --git a/test/extension/Jamfile b/test/extension/Jamfile index cf9ef40896..5754513279 100644 --- a/test/extension/Jamfile +++ b/test/extension/Jamfile @@ -7,6 +7,7 @@ # copy at http://www.boost.org/LICENSE_1_0.txt) build-project dynamic_image ; +build-project histogram ; build-project numeric ; build-project toolbox ; build-project io ; diff --git a/test/extension/histogram/CMakeLists.txt b/test/extension/histogram/CMakeLists.txt new file mode 100644 index 0000000000..562d4d916c --- /dev/null +++ b/test/extension/histogram/CMakeLists.txt @@ -0,0 +1,27 @@ +# +# Copyright 2020 Debabrata Mandal +# +# Distributed under the Boost Software License, Version 1.0 +# See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt +# + +foreach(_name + histogram) + set(_test t_core_histogram_${_name}) + set(_target test_core_histogram_${_name}) + + add_executable(${_target} "") + target_sources(${_target} PRIVATE ${_name}.cpp) + target_link_libraries(${_target} + PRIVATE + gil_compile_options + gil_include_directories + gil_dependencies) + target_compile_definitions(${_target} PRIVATE BOOST_GIL_USE_CONCEPT_CHECK) + add_test(NAME ${_test} COMMAND ${_target}) + + unset(_name) + unset(_target) + unset(_test) +endforeach() diff --git a/test/extension/histogram/Jamfile b/test/extension/histogram/Jamfile new file mode 100644 index 0000000000..d7c7edf238 --- /dev/null +++ b/test/extension/histogram/Jamfile @@ -0,0 +1,11 @@ +# +# Copyright 2020 Debabrata Mandal +# +# Distributed under the Boost Software License, Version 1.0 +# See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt +# + +import testing ; + +run histogram.cpp ; diff --git a/test/extension/histogram/histogram.cpp b/test/extension/histogram/histogram.cpp new file mode 100644 index 0000000000..6b4b23951e --- /dev/null +++ b/test/extension/histogram/histogram.cpp @@ -0,0 +1,176 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Distributed under the Boost Software License, Version 1.0 +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include +#include +#include +#include + +// Supported Container types for histogram +#include +#include +#include +#include + +// Basic tests to make sure compatible container types produce +// expected output histogram. + +namespace gil = boost::gil; + +gil::gray8_image_t img1(4, 4, gil::gray8_pixel_t(1)); +gil::gray8_view_t v1 = view(img1); + +gil::rgb8_image_t img2(4, 4, gil::rgb8_pixel_t(1)); +gil::rgb8_view_t v2 = view(img2); + +gil::gray16_image_t img3(4, 4, gil::gray16_pixel_t(1)); +gil::gray16_view_t v3 = view(img3); + +template +bool check_equal(T &cont1, T &cont2, std::size_t size) +{ + bool ch = true; + for(std::size_t i = 0; i < size; ++i) + { + ch = ch & (cont1[i] == cont2[i]); + } + return ch; +} + +template +bool check_equal(T &cont1, T &cont2) +{ + bool ch = true; + for(auto &it : cont1) + { + ch = ch & (cont1[it.first] == cont2[it.first]); + } + return ch; +} + +void check_fill_histogram_vector() +{ + std::vector c1, c1_expected(256,0); + c1_expected[1] = 16; + gil::fill_histogram(v1, c1); + BOOST_TEST(check_equal(c1, c1_expected, c1_expected.size())); + + c1_expected[1] = 32; + gil::fill_histogram(v1, c1, true); + BOOST_TEST(check_equal(c1, c1_expected, c1_expected.size())); + + std::vector c2, c2_expected; + gil::fill_histogram(v2, c2); + gil::fill_histogram(gil::color_converted_view(v2), c2_expected); + BOOST_TEST(check_equal(c2, c2_expected, c2_expected.size())); +} + + +void check_fill_histogram_array() +{ + std::array c1{0}, c1_expected{0}; + c1_expected[1] = 16; + gil::fill_histogram(v1, c1); + BOOST_TEST(check_equal(c1, c1_expected, c1_expected.size())); + + c1_expected[1] = 32; + gil::fill_histogram(v1, c1, true); + BOOST_TEST(check_equal(c1, c1_expected, c1_expected.size())); + + std::array c2{0}, c2_expected{0}; + gil::fill_histogram(v1, c2); + gil::fill_histogram(gil::color_converted_view(v2), c2_expected); + BOOST_TEST(check_equal(c2, c2_expected, c2_expected.size())); + + // Check binning + std::array c3{0}, c3_expected{0}; + c3_expected[0] = 16; + gil::fill_histogram(v3, c3); + BOOST_TEST(check_equal(c3, c3_expected, c3_expected.size())); +} + +void check_fill_histogram_map() +{ + std::map c1, c1_expected; + c1_expected[1] = 16; + gil::fill_histogram(v1, c1); + BOOST_TEST(check_equal(c1, c1_expected)); + + c1_expected[1] = 32; + gil::fill_histogram(v1, c1, true); + BOOST_TEST(check_equal(c1, c1_expected)); + + std::map c2, c2_expected; + gil::fill_histogram(v2, c2); + gil::fill_histogram(gil::color_converted_view(v2), c2_expected); + BOOST_TEST(check_equal(c2, c2_expected)); +} + +void check_cumulative_histogram_vector() +{ + std::vector v(8); + for (std::size_t i = 0; i < v.size(); i++) + { + v[i] = 1; + } + auto v1 = gil::cumulative_histogram(v); + bool check = true; + for (std::size_t i = 0; i < v.size(); i++) + { + if(v1[i] != int(i) + 1) + check = false; + } + BOOST_TEST(check); +} + +void check_cumulative_histogram_array() +{ + std::array arr; + for (std::size_t i = 0; i < arr.size(); i++) + { + arr[i] = 1; + } + auto arr1 = gil::cumulative_histogram(arr); + bool check = true; + for (std::size_t i = 0; i < arr.size(); i++) + { + if(arr1[i] != int(i) + 1) + check = false; + } + BOOST_TEST(check); +} + +void check_cumulative_histogram_map() +{ + std::map mp; + for (std::size_t i = 0; i < 8; i++) + { + mp[i] = 1; + } + auto mp1 = gil::cumulative_histogram(mp); + bool check = true; + for (std::size_t i = 0; i < mp.size(); i++) + { + if(mp1[i] != int(i) + 1) + check = false; + } + BOOST_TEST(check); +} + +int main() +{ + check_fill_histogram_vector(); + check_fill_histogram_array(); + check_fill_histogram_map(); + + check_cumulative_histogram_vector(); + check_cumulative_histogram_array(); + check_cumulative_histogram_map(); + + return boost::report_errors(); +}