diff --git a/include/highfive/bits/H5Slice_traits.hpp b/include/highfive/bits/H5Slice_traits.hpp index 4d40b7797..65c0d39e0 100644 --- a/include/highfive/bits/H5Slice_traits.hpp +++ b/include/highfive/bits/H5Slice_traits.hpp @@ -13,6 +13,7 @@ #include "H5_definitions.hpp" #include "H5Utils.hpp" +#include "convert_size_vector.hpp" #include "../H5PropertyList.hpp" #include "h5s_wrapper.hpp" @@ -51,17 +52,6 @@ class ElementSet { friend class SliceTraits; }; -namespace detail { - -template -inline std::vector convertSizeVector(const std::vector& from) { - std::vector to(from.size()); - std::copy(from.cbegin(), from.cend(), to.begin()); - - return to; -} -} // namespace detail - inline std::vector toHDF5SizeVector(const std::vector& from) { return detail::convertSizeVector(from); } diff --git a/include/highfive/bits/convert_size_vector.hpp b/include/highfive/bits/convert_size_vector.hpp new file mode 100644 index 000000000..62a815ae5 --- /dev/null +++ b/include/highfive/bits/convert_size_vector.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (c), 2017, Adrien Devresse + * Copyright (c), 2017-2024, BlueBrain Project, EPFL + * + * 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) + * + */ +#pragma once + +#include + +namespace HighFive { +namespace detail { + +template +inline std::vector convertSizeVector(const It& begin, const It& end) { + std::vector to(static_cast(end - begin)); + std::copy(begin, end, to.begin()); + + return to; +} + +template +inline std::vector convertSizeVector(const std::vector& from) { + return convertSizeVector(from.cbegin(), from.cend()); +} + +} // namespace detail +} // namespace HighFive diff --git a/include/highfive/experimental/opencv.hpp b/include/highfive/experimental/opencv.hpp new file mode 100644 index 000000000..224160975 --- /dev/null +++ b/include/highfive/experimental/opencv.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include "../bits/H5Inspector_decl.hpp" +#include "../H5Exception.hpp" + +#include + +#include "../bits/convert_size_vector.hpp" + +namespace HighFive { +namespace details { + + +template +struct inspector> { + using type = cv::Mat_; + using value_type = T; + using base_type = typename inspector::base_type; + using hdf5_type = base_type; + + static void assert_row_major(const type& type) { + // Documentation claims that Mat_ is always row-major. However, it + // could be padded. The steps/strides are in bytes. + int rank = type.dims; + size_t ld = sizeof(T); + for (int i = rank - 1; i >= 0; --i) { + if (static_cast(type.step[i]) != ld) { + throw DataSetException("Padded cv::Mat_ are not supported."); + } + + ld *= static_cast(type.size[i]); + } + } + + + static constexpr size_t min_ndim = 2 + inspector::min_ndim; + static constexpr size_t max_ndim = 1024 + inspector::max_ndim; + + // HighFive doesn't support padded OpenCV arrays. Therefore, pretend + // that they themselves are trivially copyable. And error out if the + // assumption is violated. + static constexpr bool is_trivially_copyable = std::is_trivially_copyable::value && + inspector::is_trivially_nestable; + static constexpr bool is_trivially_nestable = false; + + static size_t getRank(const type& val) { + if (val.empty()) { + return min_ndim; + + } else { + return static_cast(val.dims) + + inspector::getRank(getAnyElement(val)); + } + } + + static const T& getAnyElement(const type& val) { + return *reinterpret_cast(val.data); + } + + static T& getAnyElement(type& val) { + return *reinterpret_cast(val.data); + } + + static size_t getLocalRank(const type& val) { + return static_cast(val.dims); + } + + static std::vector getDimensions(const type& val) { + auto local_rank = getLocalRank(val); + auto rank = getRank(val); + std::vector dims(rank, 1ul); + + if (val.empty()) { + dims[0] = 0ul; + dims[1] = 1ul; + return dims; + } + + for (size_t i = 0; i < local_rank; ++i) { + dims[i] = static_cast(val.size[static_cast(i)]); + } + + auto s = inspector::getDimensions(getAnyElement(val)); + std::copy(s.cbegin(), s.cend(), dims.begin() + static_cast(local_rank)); + return dims; + } + + static void prepare(type& val, const std::vector& dims) { + auto subdims = detail::convertSizeVector(dims); + val.create(static_cast(subdims.size()), subdims.data()); + } + + static hdf5_type* data(type& val) { + assert_row_major(val); + + if (!is_trivially_copyable) { + throw DataSetException("Invalid used of `inspector>::data`."); + } + + if (val.empty()) { + return nullptr; + } + + return inspector::data(getAnyElement(val)); + } + + static const hdf5_type* data(const type& val) { + assert_row_major(val); + + if (!is_trivially_copyable) { + throw DataSetException("Invalid used of `inspector>::data`."); + } + + if (val.empty()) { + return nullptr; + } + + return inspector::data(getAnyElement(val)); + } + + static void serialize(const type& val, const std::vector& dims, hdf5_type* m) { + if (val.empty()) { + return; + } + + auto local_rank = val.dims; + auto subdims = std::vector(dims.begin() + local_rank, dims.end()); + auto subsize = compute_total_size(subdims); + for (auto it = val.begin(); it != val.end(); ++it) { + inspector::serialize(*it, subdims, m); + m += subsize; + } + } + + static void unserialize(const hdf5_type* vec_align, + const std::vector& dims, + type& val) { + auto local_rank = val.dims; + auto subdims = std::vector(dims.begin() + local_rank, dims.end()); + auto subsize = compute_total_size(subdims); + for (auto it = val.begin(); it != val.end(); ++it) { + inspector::unserialize(vec_align, subdims, *it); + vec_align += subsize; + } + } +}; + +} // namespace details +} // namespace HighFive diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index a282da319..0f795812d 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -6,7 +6,7 @@ if(MSVC) endif() ## Base tests -foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_five_easy test_all_types test_high_five_selection tests_high_five_data_type test_empty_arrays test_legacy test_string) +foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_five_easy test_all_types test_high_five_selection tests_high_five_data_type test_empty_arrays test_legacy test_opencv test_string) add_executable(${test_name} "${test_name}.cpp") target_link_libraries(${test_name} HighFive HighFiveWarnings HighFiveFlags Catch2::Catch2WithMain) target_link_libraries(${test_name} HighFiveOptionalDependencies) @@ -63,6 +63,10 @@ foreach(PUBLIC_HEADER ${public_headers}) continue() endif() + if(PUBLIC_HEADER STREQUAL "highfive/opencv.hpp" AND NOT HIGHFIVE_TEST_OPENCV) + continue() + endif() + get_filename_component(CLASS_NAME ${PUBLIC_HEADER} NAME_WE) configure_file(tests_import_public_headers.cpp "tests_${CLASS_NAME}.cpp" @ONLY) add_executable("tests_include_${CLASS_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/tests_${CLASS_NAME}.cpp") diff --git a/tests/unit/test_opencv.cpp b/tests/unit/test_opencv.cpp new file mode 100644 index 000000000..b27c06daa --- /dev/null +++ b/tests/unit/test_opencv.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c), 2024, Blue Brain Project - EPFL + * + * 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) + * + */ + +#if HIGHFIVE_TEST_OPENCV + +#include +#include +#include + +#include +#include +#include "tests_high_five.hpp" +#include "create_traits.hpp" + +using namespace HighFive; +using Catch::Matchers::Equals; + +TEST_CASE("OpenCV") { + auto file = File("rw_opencv.h5", File::Truncate); + + auto a = cv::Mat_(3, 5); + auto dset = file.createDataSet("a", a); + auto b = dset.read>(); + REQUIRE(a(0, 0) == b(0, 0)); + + auto va = std::vector>(7, cv::Mat_(3, 5)); + auto vdset = file.createDataSet("va", va); + auto vb = vdset.read>>(); + REQUIRE(vb.size() == va.size()); + REQUIRE(vb[0](0, 0) == va[0](0, 0)); +} + +TEST_CASE("OpenCV subarrays") { + auto file = File("rw_opencv_subarray.h5", File::Truncate); + + auto a = cv::Mat_(3, 13); + + SECTION("write") { + auto sa = cv::Mat_(a.colRange(1, 4)); + REQUIRE_THROWS(file.createDataSet("a", sa)); + } + + SECTION("read") { + auto b = cv::Mat_(3, 17); + auto sb = cv::Mat_(a.colRange(0, 13)); + auto dset = file.createDataSet("a", a); + + // Creates a new `Mat_` in `sb`. + dset.read(sb); + } +} + +#endif