This is a follow-up question for two_input_map_reduce Template Function Implementation in C++ and euclidean_distance Template Function Implementation for Image in C++. Considering the opinion from G. Sliepen's answer:
What if you have an RGB image? I still think it makes sense to talk about euclidian distance in that case.
I am trying to implement another overload euclidean_distance
template function which returns an std::tuple
for color image and I found that apply_each_single_output
template function for computing each plane then get an output is what I need.
The experimental implementation
apply_each_single_output
template function implementation// apply_each_single_output template function implementation template<class F, class... Args> constexpr static auto apply_each_single_output(const Image<RGB>& input1, const Image<RGB>& input2, F operation, Args&&... args) { auto Rresult = std::async(std::launch::async, [&] { return std::invoke(operation, getRplane(input1), getRplane(input2), args...); }); auto Gresult = std::async(std::launch::async, [&] { return std::invoke(operation, getGplane(input1), getGplane(input2), args...); }); auto Bresult = std::async(std::launch::async, [&] { return std::invoke(operation, getBplane(input1), getBplane(input2), args...); }); return std::make_tuple(Rresult.get(), Gresult.get(), Bresult.get()); } // apply_each_single_output template function implementation template<class F, class... Args> constexpr static auto apply_each_single_output(const Image<RGB_DOUBLE>& input1, const Image<RGB_DOUBLE>& input2, F operation, Args&&... args) { auto Rresult = std::async(std::launch::async, [&] { return std::invoke(operation, getRplane(input1), getRplane(input2), args...); }); auto Gresult = std::async(std::launch::async, [&] { return std::invoke(operation, getGplane(input1), getGplane(input2), args...); }); auto Bresult = std::async(std::launch::async, [&] { return std::invoke(operation, getBplane(input1), getBplane(input2), args...); }); return std::make_tuple(Rresult.get(), Gresult.get(), Bresult.get()); } // apply_each_single_output template function implementation template<class F, class... Args> constexpr static auto apply_each_single_output(const Image<HSV> input1, const Image<HSV> input2, F operation, Args&&... args) { auto Hresult = std::async(std::launch::async, [&] { return std::invoke(operation, getHplane(input1), getHplane(input2), args...); }); auto Sresult = std::async(std::launch::async, [&] { return std::invoke(operation, getSplane(input1), getSplane(input2), args...); }); auto Vresult = std::async(std::launch::async, [&] { return std::invoke(operation, getVplane(input1), getVplane(input2), args...); }); return std::make_tuple(Hresult.get(), Sresult.get(), Vresult.get()); }
Q&A
Question: Why using tuple as the return type, not
RGB
,RGB_DOUBLE
orHSV
as follows?// apply_each_single_output template function implementation template<class F, class... Args> constexpr static auto apply_each_single_output(const Image<RGB>& input1, const Image<RGB>& input2, F operation, Args&&... args) { auto Rresult = std::async(std::launch::async, [&] { return std::invoke(operation, getRplane(input1), getRplane(input2), args...); }); auto Gresult = std::async(std::launch::async, [&] { return std::invoke(operation, getGplane(input1), getGplane(input2), args...); }); auto Bresult = std::async(std::launch::async, [&] { return std::invoke(operation, getBplane(input1), getBplane(input2), args...); }); return RGB{Rresult.get(), Gresult.get(), Bresult.get()}; }
Answer: Because there are various possible return types in each channel based on the input lambda function, it's bad to restrict type to only
std::uint8_t
,double
anddouble
(which are the element type ofRGB
,RGB_DOUBLE
andHSV
, respectively).
Full Testing Code
The full testing code:
// apply_each_single_output Template Function Implementation for Image in C++ // Developed by Jimmy Hu #include <algorithm> #include <cassert> #include <chrono> #include <cmath> #include <complex> #include <concepts> #include <execution> #include <filesystem> #include <fstream> #include <functional> #include <future> #include <iostream> #include <iterator> #include <numeric> #include <ranges> #include <random> #include <string> #include <type_traits> #include <variant> #include <vector> #include <utility> namespace TinyDIP { struct RGB { std::uint8_t channels[3]; inline RGB operator+(const RGB& input) const { return RGB{ static_cast<std::uint8_t>(input.channels[0] + channels[0]), static_cast<std::uint8_t>(input.channels[1] + channels[1]), static_cast<std::uint8_t>(input.channels[2] + channels[2]) }; } inline RGB operator-(const RGB& input) const { return RGB{ static_cast<std::uint8_t>(channels[0] - input.channels[0]), static_cast<std::uint8_t>(channels[1] - input.channels[1]), static_cast<std::uint8_t>(channels[2] - input.channels[2]) }; } friend std::ostream& operator<<(std::ostream& out, const RGB& _myStruct) { out << '{' << +_myStruct.channels[0] << ", " << +_myStruct.channels[1] << ", " << +_myStruct.channels[2] << '}'; return out; } }; struct RGB_DOUBLE { double channels[3]; inline RGB_DOUBLE operator+(const RGB_DOUBLE& input) const { return RGB_DOUBLE{ input.channels[0] + channels[0], input.channels[1] + channels[1], input.channels[2] + channels[2] }; } inline RGB_DOUBLE operator-(const RGB_DOUBLE& input) const { return RGB_DOUBLE{ channels[0] - input.channels[0], channels[1] - input.channels[1], channels[2] - input.channels[2] }; } friend std::ostream& operator<<(std::ostream& out, const RGB_DOUBLE& _myStruct) { out << '{' << +_myStruct.channels[0] << ", " << +_myStruct.channels[1] << ", " << +_myStruct.channels[2] << '}'; return out; } }; using GrayScale = std::uint8_t; struct HSV { double channels[3]; // Range: 0 <= H < 360, 0 <= S <= 1, 0 <= V <= 255 inline HSV operator+(const HSV& input) const { return HSV{ input.channels[0] + channels[0], input.channels[1] + channels[1], input.channels[2] + channels[2] }; } inline HSV operator-(const HSV& input) const { return HSV{ channels[0] - input.channels[0], channels[1] - input.channels[1], channels[2] - input.channels[2] }; } friend std::ostream& operator<<(std::ostream& out, const HSV& _myStruct) { out << '{' << +_myStruct.channels[0] << ", " << +_myStruct.channels[1] << ", " << +_myStruct.channels[2] << '}'; return out; } }; struct BMPIMAGE { std::filesystem::path FILENAME; unsigned int XSIZE; unsigned int YSIZE; std::uint8_t FILLINGBYTE; std::uint8_t* IMAGE_DATA; }; // Reference: https://stackoverflow.com/a/48458312/6667035 template <typename> struct is_tuple : std::false_type {}; template <typename ...T> struct is_tuple<std::tuple<T...>> : std::true_type {}; template<typename T> concept image_element_standard_floating_point_type = std::same_as<double, T> or std::same_as<float, T> or std::same_as<long double, T> ; // Reference: https://stackoverflow.com/a/64287611/6667035 template <typename T> struct is_complex : std::false_type {}; template <typename T> struct is_complex<std::complex<T>> : std::true_type {}; // Reference: https://stackoverflow.com/a/58067611/6667035 template <typename T> concept arithmetic = std::is_arithmetic_v<T> or is_complex<T>::value; // recursive_print template function implementation template<typename T> constexpr void recursive_print(const T& input, const std::size_t level = 0) { std::cout << std::string(level, ' ') << input << '\n'; } template<std::ranges::input_range Range> constexpr void recursive_print(const Range& input, const std::size_t level = 0) { std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl; std::ranges::for_each(input, [level](auto&& element) { recursive_print(element, level + 1); }); } template <typename ElementT> class Image { public: Image() = default; template<std::same_as<std::size_t>... Sizes> Image(Sizes... sizes): size{sizes...}, image_data((1 * ... * sizes)) {} template<std::same_as<int>... Sizes> Image(Sizes... sizes) { size.reserve(sizeof...(sizes)); (size.emplace_back(sizes), ...); image_data.resize( std::reduce( std::ranges::cbegin(size), std::ranges::cend(size), std::size_t{1}, std::multiplies<>() ) ); } Image(const std::vector<std::size_t>& sizes) { if (sizes.empty()) { throw std::runtime_error("Image size vector is empty!"); } size = std::move(sizes); image_data.resize( std::reduce( std::ranges::cbegin(sizes), std::ranges::cend(sizes), std::size_t{1}, std::multiplies<>() )); } template<std::ranges::input_range Range, std::same_as<std::size_t>... Sizes> Image(const Range& input, Sizes... sizes): size{sizes...}, image_data(begin(input), end(input)) { if (image_data.size() != (1 * ... * sizes)) { throw std::runtime_error("Image data input and the given size are mismatched!"); } } template<std::same_as<std::size_t>... Sizes> Image(std::vector<ElementT>&& input, Sizes... sizes): size{sizes...}, image_data(begin(input), end(input)) { if (input.empty()) { throw std::runtime_error("Input vector is empty!"); } if (image_data.size() != (1 * ... * sizes)) { throw std::runtime_error("Image data input and the given size are mismatched!"); } } Image(const std::vector<ElementT>& input, const std::vector<std::size_t>& sizes) { if (input.empty()) { throw std::runtime_error("Input vector is empty!"); } size = std::move(sizes); image_data = std::move(input); auto count = std::reduce(std::ranges::cbegin(sizes), std::ranges::cend(sizes), 1, std::multiplies()); if (image_data.size() != count) { throw std::runtime_error("Image data input and the given size are mismatched!"); } } Image(const std::vector<ElementT>& input, std::size_t newWidth, std::size_t newHeight) { if (input.empty()) { throw std::runtime_error("Input vector is empty!"); } size.reserve(2); size.emplace_back(newWidth); size.emplace_back(newHeight); if (input.size() != newWidth * newHeight) { throw std::runtime_error("Image data input and the given size are mismatched!"); } image_data = std::move(input); // Reference: https://stackoverflow.com/a/51706522/6667035 } Image(const std::vector<std::vector<ElementT>>& input) { if (input.empty()) { throw std::runtime_error("Input vector is empty!"); } size.reserve(2); size.emplace_back(input[0].size()); size.emplace_back(input.size()); for (auto& rows : input) { image_data.insert(image_data.end(), std::ranges::begin(input), std::ranges::end(input)); // flatten } return; } // at template function implementation template<typename... Args> constexpr ElementT& at(const Args... indexInput) { return const_cast<ElementT&>(static_cast<const Image &>(*this).at(indexInput...)); } // at template function implementation // Reference: https://codereview.stackexchange.com/a/288736/231235 template<typename... Args> constexpr ElementT const& at(const Args... indexInput) const { checkBoundary(indexInput...); constexpr std::size_t n = sizeof...(Args); if(n != size.size()) { throw std::runtime_error("Dimensionality mismatched!"); } std::size_t i = 0; std::size_t stride = 1; std::size_t position = 0; auto update_position = [&](auto index) { position += index * stride; stride *= size[i++]; }; (update_position(indexInput), ...); return image_data[position]; } // at_without_boundary_check template function implementation template<typename... Args> constexpr ElementT& at_without_boundary_check(const Args... indexInput) { return const_cast<ElementT&>(static_cast<const Image &>(*this).at_without_boundary_check(indexInput...)); } template<typename... Args> constexpr ElementT const& at_without_boundary_check(const Args... indexInput) const { std::size_t i = 0; std::size_t stride = 1; std::size_t position = 0; auto update_position = [&](auto index) { position += index * stride; stride *= size[i++]; }; (update_position(indexInput), ...); return image_data[position]; } // get function implementation constexpr ElementT get(std::size_t index) const noexcept { return image_data[index]; } // set function implementation constexpr ElementT& set(const std::size_t index) noexcept { return image_data[index]; } // set template function implementation template<class TupleT> requires(is_tuple<TupleT>::value) constexpr bool set(const TupleT location, const ElementT draw_value) { if (checkBoundaryTuple(location)) { image_data[tuple_location_to_index(location)] = draw_value; return true; } return false; } // cast template function implementation template<typename TargetT> constexpr Image<TargetT> cast() { std::vector<TargetT> output_data; output_data.resize(image_data.size()); std::transform( std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::begin(output_data), [](auto& input){ return static_cast<TargetT>(input); } ); Image<TargetT> output(output_data, size); return output; } constexpr std::size_t count() const noexcept { return std::reduce(std::ranges::cbegin(size), std::ranges::cend(size), 1, std::multiplies()); } constexpr std::size_t getDimensionality() const noexcept { return size.size(); } constexpr std::size_t getWidth() const noexcept { return size[0]; } constexpr std::size_t getHeight() const noexcept { return size[1]; } // getSize function implementation constexpr auto getSize() const noexcept { return size; } // getSize function implementation constexpr auto getSize(std::size_t index) const noexcept { return size[index]; } // getStride function implementation constexpr std::size_t getStride(std::size_t index) const noexcept { if(index == 0) { return std::size_t{1}; } std::size_t output = std::size_t{1}; for(std::size_t i = 0; i < index; ++i) { output *= size[i]; } return output; } std::vector<ElementT> const& getImageData() const noexcept { return image_data; } // expose the internal data // print function implementation void print(std::string separator = "\t", std::ostream& os = std::cout) const { if(size.size() == 1) { for(std::size_t x = 0; x < size[0]; ++x) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x) << separator; } os << "\n"; } else if(size.size() == 2) { for (std::size_t y = 0; y < size[1]; ++y) { for (std::size_t x = 0; x < size[0]; ++x) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x, y) << separator; } os << "\n"; } os << "\n"; } else if (size.size() == 3) { for(std::size_t z = 0; z < size[2]; ++z) { for (std::size_t y = 0; y < size[1]; ++y) { for (std::size_t x = 0; x < size[0]; ++x) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x, y, z) << separator; } os << "\n"; } os << "\n"; } os << "\n"; } else if (size.size() == 4) { for(std::size_t a = 0; a < size[3]; ++a) { os << "group = " << a << "\n"; for(std::size_t z = 0; z < size[2]; ++z) { for (std::size_t y = 0; y < size[1]; ++y) { for (std::size_t x = 0; x < size[0]; ++x) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x, y, z, a) << separator; } os << "\n"; } os << "\n"; } os << "\n"; } os << "\n"; } } // Enable this function if ElementT = RGB void print(std::string separator = "\t", std::ostream& os = std::cout) const requires(std::same_as<ElementT, RGB>) { for (std::size_t y = 0; y < size[1]; ++y) { for (std::size_t x = 0; x < size[0]; ++x) { os << "( "; for (std::size_t channel_index = 0; channel_index < 3; ++channel_index) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x, y).channels[channel_index] << separator; } os << ")" << separator; } os << "\n"; } os << "\n"; return; } Image<ElementT>& setAllValue(const ElementT input) { std::fill(std::ranges::begin(image_data), std::ranges::end(image_data), input); return *this; } friend std::ostream& operator<<(std::ostream& os, const Image<ElementT>& rhs) { const std::string separator = "\t"; rhs.print(separator, os); return os; } Image<ElementT>& operator+=(const Image<ElementT>& rhs) { check_size_same(rhs, *this); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::plus<>{}); return *this; } Image<ElementT>& operator-=(const Image<ElementT>& rhs) { check_size_same(rhs, *this); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::minus<>{}); return *this; } Image<ElementT>& operator*=(const Image<ElementT>& rhs) { check_size_same(rhs, *this); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::multiplies<>{}); return *this; } Image<ElementT>& operator/=(const Image<ElementT>& rhs) { check_size_same(rhs, *this); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::divides<>{}); return *this; } friend bool operator==(Image<ElementT> const&, Image<ElementT> const&) = default; friend bool operator!=(Image<ElementT> const&, Image<ElementT> const&) = default; friend Image<ElementT> operator+(Image<ElementT> input1, const Image<ElementT>& input2) { return input1 += input2; } friend Image<ElementT> operator-(Image<ElementT> input1, const Image<ElementT>& input2) { return input1 -= input2; } friend Image<ElementT> operator*(Image<ElementT> input1, ElementT input2) { return multiplies(input1, input2); } friend Image<ElementT> operator*(ElementT input1, Image<ElementT> input2) { return multiplies(input2, input1); } #ifdef USE_BOOST_SERIALIZATION void Save(std::string filename) { const std::string filename_with_extension = filename + ".dat"; // Reference: https://stackoverflow.com/questions/523872/how-do-you-serialize-an-object-in-c std::ofstream ofs(filename_with_extension, std::ios::binary); boost::archive::binary_oarchive ArchiveOut(ofs); // write class instance to archive ArchiveOut << *this; // archive and stream closed when destructors are called ofs.close(); } #endif private: std::vector<std::size_t> size; std::vector<ElementT> image_data; template<typename... Args> void checkBoundary(const Args... indexInput) const { constexpr std::size_t n = sizeof...(Args); if(n != size.size()) { throw std::runtime_error("Dimensionality mismatched!"); } std::size_t parameter_pack_index = 0; auto function = [&](auto index) { if (index >= size[parameter_pack_index]) throw std::out_of_range("Given index out of range!"); parameter_pack_index = parameter_pack_index + 1; }; (function(indexInput), ...); } // checkBoundaryTuple template function implementation template<class TupleT> requires(is_tuple<TupleT>::value) constexpr bool checkBoundaryTuple(const TupleT location) { constexpr std::size_t n = std::tuple_size<TupleT>{}; if(n != size.size()) { throw std::runtime_error("Dimensionality mismatched!"); } std::size_t parameter_pack_index = 0; auto function = [&](auto index) { if (std::cmp_greater_equal(index, size[parameter_pack_index])) return false; parameter_pack_index = parameter_pack_index + 1; return true; }; return std::apply([&](auto&&... args) { return ((function(args))&& ...);}, location); } // tuple_location_to_index template function implementation template<class TupleT> requires(is_tuple<TupleT>::value) constexpr std::size_t tuple_location_to_index(TupleT location) { std::size_t i = 0; std::size_t stride = 1; std::size_t position = 0; auto update_position = [&](auto index) { position += index * stride; stride *= size[i++]; }; std::apply([&](auto&&... args) {((update_position(args)), ...);}, location); return position; } #ifdef USE_BOOST_SERIALIZATION friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar& size; ar& image_data; } #endif }; template<typename T, typename ElementT> concept is_Image = std::is_same_v<T, Image<ElementT>>; // zeros template function implementation template<typename ElementT, std::same_as<std::size_t>... Sizes> constexpr static auto zeros(Sizes... sizes) { auto output = Image<ElementT>(sizes...); return output; } // ones template function implementation template<typename ElementT, std::same_as<std::size_t>... Sizes> constexpr static auto ones(Sizes... sizes) { auto output = zeros<ElementT>(sizes...); output.setAllValue(1); return output; } // rand template function implementation template<image_element_standard_floating_point_type ElementT = double, typename Urbg, std::same_as<std::size_t>... Sizes> requires std::uniform_random_bit_generator<std::remove_reference_t<Urbg>> constexpr static auto rand(Urbg&& urbg, Sizes... sizes) { if constexpr (sizeof...(Sizes) == 1) { return rand(std::forward<Urbg>(urbg), sizes..., sizes...); } else { std::vector<ElementT> image_data((... * sizes)); // Reference: https://stackoverflow.com/a/23143753/6667035 // Reference: https://codereview.stackexchange.com/a/294739/231235 auto dist = std::uniform_real_distribution<ElementT>{}; std::ranges::generate(image_data, [&dist, &urbg]() { return dist(urbg); }); return Image<ElementT>{std::move(image_data), sizes...}; } } // rand template function implementation template<image_element_standard_floating_point_type ElementT = double, std::same_as<std::size_t>... Size> inline auto rand(Size... size) { return rand<ElementT>(std::mt19937{std::random_device{}()}, size...); } // rand template function implementation template<image_element_standard_floating_point_type ElementT = double, typename Urbg> requires std::uniform_random_bit_generator<std::remove_reference_t<Urbg>> constexpr auto rand(Urbg&& urbg) -> ElementT { auto dist = std::uniform_real_distribution<ElementT>{}; return dist(urbg); } // rand template function implementation template<image_element_standard_floating_point_type ElementT = double> inline auto rand() { return rand<ElementT>(std::mt19937{std::random_device{}()}); } template<typename ElementT> constexpr void check_width_same(const Image<ElementT>& x, const Image<ElementT>& y) { if (!is_width_same(x, y)) throw std::runtime_error("Width mismatched!"); } template<typename ElementT> constexpr void check_height_same(const Image<ElementT>& x, const Image<ElementT>& y) { if (!is_height_same(x, y)) throw std::runtime_error("Height mismatched!"); } // check_size_same template function implementation template<typename ElementT> constexpr void check_size_same(const Image<ElementT>& x, const Image<ElementT>& y) { if(x.getSize() != y.getSize()) throw std::runtime_error("Size mismatched!"); } // getPlane template function implementation template<class OutputT = unsigned char> constexpr static auto getPlane(const Image<RGB>& input, std::size_t index) { auto input_data = input.getImageData(); std::vector<OutputT> output_data; output_data.resize(input.count()); #pragma omp parallel for for (std::size_t i = 0; i < input.count(); ++i) { output_data[i] = input_data[i].channels[index]; } auto output = Image<OutputT>(output_data, input.getSize()); return output; } // getPlane template function implementation template<class T = HSV, class OutputT = double> requires (std::same_as<T, HSV> || std::same_as<T, RGB_DOUBLE>) constexpr static auto getPlane(const Image<T>& input, std::size_t index) { auto input_data = input.getImageData(); std::vector<OutputT> output_data; output_data.resize(input.count()); #pragma omp parallel for for (std::size_t i = 0; i < input.count(); ++i) { output_data[i] = input_data[i].channels[index]; } auto output = Image<OutputT>(output_data, input.getSize()); return output; } // getRplane function implementation constexpr static auto getRplane(const Image<RGB>& input) { return getPlane(input, 0); } // getRplane function implementation constexpr static auto getRplane(const Image<RGB_DOUBLE>& input) { return getPlane(input, 0); } // getGplane function implementation constexpr static auto getGplane(const Image<RGB>& input) { return getPlane(input, 1); } // getGplane function implementation constexpr static auto getGplane(const Image<RGB_DOUBLE>& input) { return getPlane(input, 1); } // getBplane function implementation constexpr static auto getBplane(const Image<RGB>& input) { return getPlane(input, 2); } // getBplane function implementation constexpr static auto getBplane(const Image<RGB_DOUBLE>& input) { return getPlane(input, 2); } constexpr static auto getHplane(const Image<HSV>& input) { return getPlane(input, 0); } constexpr static auto getSplane(const Image<HSV>& input) { return getPlane(input, 1); } constexpr static auto getVplane(const Image<HSV>& input) { return getPlane(input, 2); } // constructRGB template function implementation template<typename OutputT = RGB> constexpr static auto constructRGB(const Image<GrayScale>& r, const Image<GrayScale>& g, const Image<GrayScale>& b) { check_size_same(r, g); check_size_same(g, b); auto image_data_r = r.getImageData(); auto image_data_g = g.getImageData(); auto image_data_b = b.getImageData(); std::vector<OutputT> new_data; new_data.resize(r.count()); #pragma omp parallel for for (std::size_t index = 0; index < r.count(); ++index) { OutputT rgb { image_data_r[index], image_data_g[index], image_data_b[index]}; new_data[index] = rgb; } Image<OutputT> output(new_data, r.getSize()); return output; } // constructRGBDOUBLE template function implementation template<typename OutputT = RGB_DOUBLE> constexpr static auto constructRGBDOUBLE(const Image<double>& r, const Image<double>& g, const Image<double>& b) { check_size_same(r, g); check_size_same(g, b); auto image_data_r = r.getImageData(); auto image_data_g = g.getImageData(); auto image_data_b = b.getImageData(); std::vector<OutputT> new_data; new_data.resize(r.count()); #pragma omp parallel for for (std::size_t index = 0; index < r.count(); ++index) { OutputT rgb_double { image_data_r[index], image_data_g[index], image_data_b[index]}; new_data[index] = rgb_double; } Image<OutputT> output(new_data, r.getSize()); return output; } // constructHSV template function implementation template<typename OutputT = HSV> constexpr static auto constructHSV(const Image<double>& h, const Image<double>& s, const Image<double>& v) { check_size_same(h, s); check_size_same(s, v); auto image_data_h = h.getImageData(); auto image_data_s = s.getImageData(); auto image_data_v = v.getImageData(); std::vector<OutputT> new_data; new_data.resize(h.count()); #pragma omp parallel for for (std::size_t index = 0; index < h.count(); ++index) { OutputT hsv { image_data_h[index], image_data_s[index], image_data_v[index]}; new_data[index] = hsv; } Image<OutputT> output(new_data, h.getSize()); return output; } // apply_each template function implementation template<class F, class... Args> constexpr static auto apply_each(const Image<RGB>& input, F operation, Args&&... args) { auto Rplane = std::async(std::launch::async, [&] { return std::invoke(operation, getRplane(input), args...); }); auto Gplane = std::async(std::launch::async, [&] { return std::invoke(operation, getGplane(input), args...); }); auto Bplane = std::async(std::launch::async, [&] { return std::invoke(operation, getBplane(input), args...); }); return constructRGB(Rplane.get(), Gplane.get(), Bplane.get()); } // apply_each template function implementation template<class F, class... Args> constexpr static auto apply_each(const Image<RGB_DOUBLE>& input, F operation, Args&&... args) { auto Rplane = std::async(std::launch::async, [&] { return std::invoke(operation, getRplane(input), args...); }); auto Gplane = std::async(std::launch::async, [&] { return std::invoke(operation, getGplane(input), args...); }); auto Bplane = std::async(std::launch::async, [&] { return std::invoke(operation, getBplane(input), args...); }); return constructRGBDOUBLE(Rplane.get(), Gplane.get(), Bplane.get()); } // apply_each template function implementation template<class F, class... Args> constexpr static auto apply_each(const Image<HSV>& input, F operation, Args&&... args) { auto Hplane = std::async(std::launch::async, [&] { return std::invoke(operation, getHplane(input), args...); }); auto Splane = std::async(std::launch::async, [&] { return std::invoke(operation, getSplane(input), args...); }); auto Vplane = std::async(std::launch::async, [&] { return std::invoke(operation, getVplane(input), args...); }); return constructHSV(Hplane.get(), Splane.get(), Vplane.get()); } // apply_each template function implementation template<class F, class... Args> constexpr static auto apply_each(const Image<RGB>& input1, const Image<RGB>& input2, F operation, Args&&... args) { auto Rplane = std::async(std::launch::async, [&] { return std::invoke(operation, getRplane(input1), getRplane(input2), args...); }); auto Gplane = std::async(std::launch::async, [&] { return std::invoke(operation, getGplane(input1), getGplane(input2), args...); }); auto Bplane = std::async(std::launch::async, [&] { return std::invoke(operation, getBplane(input1), getBplane(input2), args...); }); return constructRGB(Rplane.get(), Gplane.get(), Bplane.get()); } // apply_each template function implementation template<class F, class... Args> constexpr static auto apply_each(const Image<RGB_DOUBLE>& input1, const Image<RGB_DOUBLE>& input2, F operation, Args&&... args) { auto Rplane = std::async(std::launch::async, [&] { return std::invoke(operation, getRplane(input1), getRplane(input2), args...); }); auto Gplane = std::async(std::launch::async, [&] { return std::invoke(operation, getGplane(input1), getGplane(input2), args...); }); auto Bplane = std::async(std::launch::async, [&] { return std::invoke(operation, getBplane(input1), getBplane(input2), args...); }); return constructRGBDOUBLE(Rplane.get(), Gplane.get(), Bplane.get()); } // apply_each template function implementation template<class F, class... Args> constexpr static auto apply_each(const Image<HSV> input1, const Image<HSV> input2, F operation, Args&&... args) { auto Hplane = std::async(std::launch::async, [&] { return std::invoke(operation, getHplane(input1), getHplane(input2), args...); }); auto Splane = std::async(std::launch::async, [&] { return std::invoke(operation, getSplane(input1), getSplane(input2), args...); }); auto Vplane = std::async(std::launch::async, [&] { return std::invoke(operation, getVplane(input1), getVplane(input2), args...); }); return constructHSV(Hplane.get(), Splane.get(), Vplane.get()); } // apply_each_single_output template function implementation template<class F, class... Args> constexpr static auto apply_each_single_output(const Image<RGB>& input1, const Image<RGB>& input2, F operation, Args&&... args) { auto Rresult = std::async(std::launch::async, [&] { return std::invoke(operation, getRplane(input1), getRplane(input2), args...); }); auto Gresult = std::async(std::launch::async, [&] { return std::invoke(operation, getGplane(input1), getGplane(input2), args...); }); auto Bresult = std::async(std::launch::async, [&] { return std::invoke(operation, getBplane(input1), getBplane(input2), args...); }); return std::make_tuple(Rresult.get(), Gresult.get(), Bresult.get()); } // apply_each_single_output template function implementation template<class F, class... Args> constexpr static auto apply_each_single_output(const Image<RGB_DOUBLE>& input1, const Image<RGB_DOUBLE>& input2, F operation, Args&&... args) { auto Rresult = std::async(std::launch::async, [&] { return std::invoke(operation, getRplane(input1), getRplane(input2), args...); }); auto Gresult = std::async(std::launch::async, [&] { return std::invoke(operation, getGplane(input1), getGplane(input2), args...); }); auto Bresult = std::async(std::launch::async, [&] { return std::invoke(operation, getBplane(input1), getBplane(input2), args...); }); return std::make_tuple(Rresult.get(), Gresult.get(), Bresult.get()); } // apply_each_single_output template function implementation template<class F, class... Args> constexpr static auto apply_each_single_output(const Image<HSV> input1, const Image<HSV> input2, F operation, Args&&... args) { auto Hresult = std::async(std::launch::async, [&] { return std::invoke(operation, getHplane(input1), getHplane(input2), args...); }); auto Sresult = std::async(std::launch::async, [&] { return std::invoke(operation, getSplane(input1), getSplane(input2), args...); }); auto Vresult = std::async(std::launch::async, [&] { return std::invoke(operation, getVplane(input1), getVplane(input2), args...); }); return std::make_tuple(Hresult.get(), Sresult.get(), Vresult.get()); } // two_input_map_reduce Template Function Implementation template< class ExecutionPolicy, std::ranges::input_range Input1, std::ranges::input_range Input2, class T, class BinaryOp1 = std::minus<T>, class BinaryOp2 = std::plus<T> > requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>) constexpr auto two_input_map_reduce( ExecutionPolicy execution_policy, const Input1& input1, const Input2& input2, const T init = {}, const BinaryOp1& binop1 = std::minus<T>(), const BinaryOp2& binop2 = std::plus<T>()) { if (input1.size() != input2.size()) { throw std::runtime_error("Size mismatched!"); } auto transformed = std::views::zip(input1, input2) | std::views::transform([&](auto input) { return std::invoke(binop1, std::get<0>(input), std::get<1>(input)); }); return std::reduce( execution_policy, transformed.begin(), transformed.end(), init, binop2 ); } // euclidean_distance Template Function Implementation template< arithmetic OutputT = double, class ExPo, arithmetic ElementT1 = double, arithmetic ElementT2 = double > requires(std::is_execution_policy_v<std::remove_cvref_t<ExPo>>) constexpr static auto euclidean_distance( ExPo execution_policy, const Image<ElementT1>& input1, const Image<ElementT2>& input2, const OutputT output = 0.0 ) { if (input1.getSize() != input2.getSize()) { throw std::runtime_error("Size mismatched!"); } return std::sqrt(two_input_map_reduce(execution_policy, input1.getImageData(), input2.getImageData(), OutputT{}, [&](auto&& element1, auto&& element2) { return std::pow(element1 - element2, 2.0); })); } // euclidean_distance Template Function Implementation template< arithmetic OutputT = double, arithmetic ElementT1 = double, arithmetic ElementT2 = double > constexpr static auto euclidean_distance( const Image<ElementT1>& input1, const Image<ElementT2>& input2, const OutputT output = 0.0 ) { return euclidean_distance(std::execution::seq, input1, input2, output); } // euclidean_distance Template Function Implementation for multiple channel image template< arithmetic OutputT = double, class ElementT1, class ElementT2 > requires((std::same_as<ElementT1, RGB>) || (std::same_as<ElementT1, RGB_DOUBLE>) || (std::same_as<ElementT1, HSV>)) and ((std::same_as<ElementT2, RGB>) || (std::same_as<ElementT2, RGB_DOUBLE>) || (std::same_as<ElementT2, HSV>)) constexpr static auto euclidean_distance( const Image<ElementT1>& input1, const Image<ElementT2>& input2, const OutputT output = 0.0 ) { return apply_each_single_output(input1, input2, [&](auto&& planes1, auto&& planes2) { return euclidean_distance(planes1, planes2, output); }); } // hypot Template Function Implementation template<typename... Args> constexpr auto hypot(Args... args) { return std::sqrt((std::pow(args, 2.0) + ...)); } // Copy from https://stackoverflow.com/a/41171552 template<class TupType, std::size_t... I> void print_tuple(const TupType& _tup, std::index_sequence<I...>) { std::cout << "("; (..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup))); std::cout << ")\n"; } template<class... T> void print_tuple(const std::tuple<T...>& _tup) { print_tuple(_tup, std::make_index_sequence<sizeof...(T)>()); } } void euclidean_distanceRGBTest( const std::size_t sizex = 3, const std::size_t sizey = 2 ) { TinyDIP::Image<TinyDIP::RGB> image1(sizex, sizey); image1.setAllValue(TinyDIP::RGB{1, 2, 3}); image1.print(); TinyDIP::Image<TinyDIP::RGB> image2(sizex, sizey); image2.print(); std::cout << "euclidean_distance of image1 and image2: " << '\n'; TinyDIP::print_tuple(TinyDIP::euclidean_distance(image1, image2)); return; } int main() { auto start = std::chrono::system_clock::now(); euclidean_distanceRGBTest(); auto end = std::chrono::system_clock::now(); std::chrono::duration<double> elapsed_seconds = end - start; std::time_t end_time = std::chrono::system_clock::to_time_t(end); if (elapsed_seconds.count() != 1) { std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << " seconds.\n"; } else { std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << " second.\n"; } return EXIT_SUCCESS; }
The output of the test code above:
( 1 2 3 ) ( 1 2 3 ) ( 1 2 3 ) ( 1 2 3 ) ( 1 2 3 ) ( 1 2 3 ) ( 0 0 0 ) ( 0 0 0 ) ( 0 0 0 ) ( 0 0 0 ) ( 0 0 0 ) ( 0 0 0 ) euclidean_distance of image1 and image2: (2.44949, 4.89898, 7.34847) Computation finished at Thu Dec 26 17:19:51 2024 elapsed time: 0.0002009 seconds.
All suggestions are welcome.
The summary information:
Which question it is a follow-up to?
two_input_map_reduce Template Function Implementation in C++ and
euclidean_distance Template Function Implementation for Image in C++.
What changes has been made in the code since last question?
I am trying to implement
apply_each_single_output
template function in this post.Why a new review is being asked for?
Please check the implementation of
apply_each_single_output
template function and the related code.