// // 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 #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 auto hash_tuple_impl(std::size_t&, std::tuple const&) -> typename std::enable_if::type { // terminating case } /// \ingroup Histogram-Helpers /// template inline auto hash_tuple_impl(std::size_t& seed, std::tuple const& t) -> typename std::enable_if::type { 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 { auto operator()(std::tuple 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 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); static_cast(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 auto cumulative_histogram(Container const&) -> Container; template auto cumulative_histogram(histogram const& hist) -> histogram { 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