// // Copyright 2020 Debabrata Mandal <mandaldebabrata123@gmail.com> // // 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 <boost/gil/concepts/concept_check.hpp> #include <boost/gil/metafunctions.hpp> #include <boost/gil/pixel.hpp> #include <boost/mp11.hpp> #include <boost/type_traits.hpp> #include <boost/functional/hash.hpp> #include <array> #include <iostream> #include <tuple> #include <utility> #include <vector> #include <type_traits> #include <map> #include <unordered_map> 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 <std::size_t Index, typename... T> inline auto hash_tuple_impl(std::size_t&, std::tuple<T...> const&) -> typename std::enable_if<Index == sizeof...(T), void>::type { // terminating case } /// \ingroup Histogram-Helpers /// template <std::size_t Index, typename... T> inline auto hash_tuple_impl(std::size_t& seed, std::tuple<T...> const& t) -> typename std::enable_if<Index != sizeof...(T), void>::type { boost::hash_combine(seed, std::get<Index>(t)); hash_tuple_impl<Index + 1>(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 <typename... T> struct hash_tuple { auto operator()(std::tuple<T...> const& t) const -> std::size_t { 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 <typename Pixel, std::size_t... I> auto pixel_to_tuple(Pixel const& p, boost::mp11::index_sequence<I...>) -> 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 <typename Tuple, std::size_t... I> auto tuple_to_tuple(Tuple const& t, boost::mp11::index_sequence<I...>) -> decltype(std::make_tuple(std::get<I>(t)...)) { return std::make_tuple(std::get<I>(t)...); } /// \ingroup Histogram-Helpers /// template <typename Tuple, std::size_t... I> bool tuple_compare(Tuple const& t1, Tuple const& t2, boost::mp11::index_sequence<I...>) { std::array<bool, std::tuple_size<Tuple>::value> comp_list; comp_list = {std::get<I>(t1) <= std::get<I>(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 <typename Tuple> bool tuple_compare(Tuple const& t1, Tuple const& t2) { std::size_t const tuple_size = std::tuple_size<Tuple>::value; auto index_list = boost::mp11::make_index_sequence<tuple_size>{}; 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 <typename Tuple> struct tuple_limit { static constexpr Tuple (min)() { return min_impl(boost::mp11::make_index_sequence<std::tuple_size<Tuple>::value>{}); } static constexpr Tuple (max)() { return max_impl(boost::mp11::make_index_sequence<std::tuple_size<Tuple>::value>{}); } private: template <std::size_t... I> static constexpr Tuple min_impl(boost::mp11::index_sequence<I...>) { return std::make_tuple( (std::numeric_limits<typename std::tuple_element<I, Tuple>::type>::min)()...); } template <std::size_t... I> static constexpr Tuple max_impl(boost::mp11::index_sequence<I...>) { return std::make_tuple( (std::numeric_limits<typename std::tuple_element<I, Tuple>::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 <std::size_t Dimension> struct filler { template <typename Container, typename Tuple> void operator()(Container&, Tuple&, Tuple&, std::size_t) { } }; /// \ingroup Histogram-Helpers /// \brief Specialisation for 1D histogram. template <> struct filler<1> { template <typename Container, typename Tuple> void operator()(Container& hist, Tuple& lower, Tuple& upper, std::size_t bin_width = 1) { for (auto i = std::get<0>(lower); static_cast<std::size_t>(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<int, int , int> 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 <typename... T> class histogram : public std::unordered_map<std::tuple<T...>, double, detail::hash_tuple<T...>> { using base_t = std::unordered_map<std::tuple<T...>, double, detail::hash_tuple<T...>>; using bin_t = boost::mp11::mp_list<T...>; 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<key_t>::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<std::tuple<T...>>::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 <typename OtherType> 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<T...>; return boost::mp11::mp_all_of<bin_types, std::is_arithmetic>::value; } /// \brief Checks if the histogram class is compatible to be used with /// the specified tuple type template <typename Tuple> bool is_tuple_compatible(Tuple const&) { std::size_t const tuple_size = std::tuple_size<Tuple>::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<histogram_size>, boost::mp11::make_index_sequence<tuple_size> >::type; if (is_tuple_size_compatible<Tuple>()) return is_tuple_type_compatible<Tuple>(sequence_type{}); else return false; } /// \brief Returns a key compatible to be used as the histogram key /// from the input tuple template <std::size_t... Dimensions, typename Tuple> key_t key_from_tuple(Tuple const& t) const { using index_list = boost::mp11::mp_list_c<std::size_t, Dimensions...>; std::size_t const index_list_size = boost::mp11::mp_size<index_list>::value; std::size_t const tuple_size = std::tuple_size<Tuple>::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<std::size_t, 0>, index_list >::type; std::size_t const min = boost::mp11::mp_min_element<new_index_list, boost::mp11::mp_less>::value; std::size_t const max = boost::mp11::mp_max_element<new_index_list, boost::mp11::mp_less>::value; static_assert((0 <= min && max < tuple_size) || index_list_size == 0, "Index out of Range"); using seq1 = boost::mp11::make_index_sequence<histogram_dimension>; using seq2 = boost::mp11::index_sequence<Dimensions...>; // TODO : Explore consequence of using if-constexpr using sequence_type = typename std::conditional<index_list_size == 0, seq1, seq2>::type; auto key = detail::tuple_to_tuple(t, sequence_type{}); static_assert( is_tuple_type_compatible<Tuple>(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 <std::size_t... Dimensions, typename Pixel> key_t key_from_pixel(Pixel const& p) const { using index_list = boost::mp11::mp_list_c<std::size_t, Dimensions...>; std::size_t const index_list_size = boost::mp11::mp_size<index_list>::value; std::size_t const pixel_dimension = num_channels<Pixel>::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<std::size_t, 0>, index_list >::type; std::size_t const min = boost::mp11::mp_min_element<new_index_list, boost::mp11::mp_less>::value; std::size_t const max = boost::mp11::mp_max_element<new_index_list, boost::mp11::mp_less>::value; static_assert( (0 <= min && max < pixel_dimension) || index_list_size == 0, "Index out of Range"); using seq1 = boost::mp11::make_index_sequence<histogram_dimension>; using seq2 = boost::mp11::index_sequence<Dimensions...>; using sequence_type = typename std::conditional<index_list_size == 0, seq1, seq2>::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<boost::has_less<T>...>; static_assert( boost::mp11::mp_all_of<check_list, boost::mp11::mp_to_bool>::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 <std::size_t... Dimensions, typename SrcView> void fill( SrcView const& srcview, std::size_t bin_width = 1, bool applymask = false, std::vector<std::vector<bool>> mask = {}, key_t lower = key_t(), key_t upper = key_t(), bool setlimits = false) { gil_function_requires<ImageViewConcept<SrcView>>(); using channel_t = typename channel_type<SrcView>::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<Dimensions...>(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 <std::size_t... Dimensions, typename Tuple> histogram sub_histogram(Tuple const& t1, Tuple const& t2) { using index_list = boost::mp11::mp_list_c<std::size_t, Dimensions...>; std::size_t const index_list_size = boost::mp11::mp_size<index_list>::value; std::size_t const histogram_dimension = dimension(); std::size_t const min = boost::mp11::mp_min_element<index_list, boost::mp11::mp_less>::value; std::size_t const max = boost::mp11::mp_max_element<index_list, boost::mp11::mp_less>::value; static_assert( (0 <= min && max < histogram_dimension) && index_list_size < histogram_dimension, "Index out of Range"); using seq1 = boost::mp11::make_index_sequence<dimension()>; using seq2 = boost::mp11::index_sequence<Dimensions...>; static_assert( is_tuple_type_compatible<Tuple>(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 <std::size_t... Dimensions> histogram<boost::mp11::mp_at<bin_t, boost::mp11::mp_size_t<Dimensions>>...> sub_histogram() { using index_list = boost::mp11::mp_list_c<std::size_t, Dimensions...>; std::size_t const index_list_size = boost::mp11::mp_size<index_list>::value; std::size_t const histogram_dimension = dimension(); std::size_t const min = boost::mp11::mp_min_element<index_list, boost::mp11::mp_less>::value; std::size_t const max = boost::mp11::mp_max_element<index_list, boost::mp11::mp_less>::value; static_assert( (0 <= min && max < histogram_dimension) && index_list_size < histogram_dimension, "Index out of Range"); histogram<boost::mp11::mp_at<bin_t, boost::mp11::mp_size_t<Dimensions>>...> 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<Dimensions...>{}); 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<key_t> sorted_keys() const { std::vector<key_t> 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 <typename Tuple, std::size_t... I> key_t make_histogram_key(Tuple const& t, boost::mp11::index_sequence<I...>) const { return std::make_tuple( static_cast<typename boost::mp11::mp_at<bin_t, boost::mp11::mp_size_t<I>>>( std::get<I>(t))...); } template <typename Tuple, std::size_t... I> static constexpr bool is_tuple_type_compatible(boost::mp11::index_sequence<I...>) { using tp = boost::mp11::mp_list < typename std::is_convertible < boost::mp11::mp_at<bin_t, boost::mp11::mp_size_t<I>>, typename std::tuple_element<I, Tuple>::type >::type... >; return boost::mp11::mp_all_of<tp, boost::mp11::mp_to_bool>::value; } template <typename Tuple> static constexpr bool is_tuple_size_compatible() { return (std::tuple_size<Tuple>::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<int> h; /// fill_histogram(view(img), h); /// \endcode /// template <typename SrcView, typename Container> 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 <std::size_t... Dimensions, typename SrcView, typename... T> void fill_histogram( SrcView const& srcview, histogram<T...>& hist, std::size_t bin_width = 1, bool accumulate = false, bool sparsefill = true, bool applymask = false, std::vector<std::vector<bool>> mask = {}, typename histogram<T...>::key_type lower = (detail::tuple_limit<typename histogram<T...>::key_type>::min)(), typename histogram<T...>::key_type upper = (detail::tuple_limit<typename histogram<T...>::key_type>::max)(), bool setlimits = false) { if (!accumulate) hist.clear(); detail::filler<histogram<T...>::dimension()> f; if (!sparsefill) f(hist, lower, upper, bin_width); hist.template fill<Dimensions...>(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 <typename Container> auto cumulative_histogram(Container const&) -> Container; template <typename... T> auto cumulative_histogram(histogram<T...> const& hist) -> histogram<T...> { using check_list = boost::mp11::mp_list<boost::has_less<T>...>; static_assert( boost::mp11::mp_all_of<check_list, boost::mp11::mp_to_bool>::value, "Cumulative histogram not possible of this type"); using histogram_t = histogram<T...>; using pair_t = std::pair<typename histogram_t::key_type, typename histogram_t::mapped_type>; 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<pair_t> 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<typename histogram_t::mapped_type>(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<typename histogram_t::mapped_type>(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<histogram_t::dimension()>{}); if (comp) cumulative_counter += hist.at(v2.first); }); cumulative_hist[v1.first] = cumulative_counter; }); } return cumulative_hist; } }} //namespace boost::gil #endif