Skip to content

Commit

Permalink
Move OpenCV support to core.
Browse files Browse the repository at this point in the history
  • Loading branch information
1uc committed May 18, 2024
1 parent 96d2100 commit 5e31ec9
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 12 deletions.
12 changes: 1 addition & 11 deletions include/highfive/bits/H5Slice_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "H5_definitions.hpp"
#include "H5Utils.hpp"
#include "convert_size_vector.hpp"

#include "../H5PropertyList.hpp"
#include "h5s_wrapper.hpp"
Expand Down Expand Up @@ -51,17 +52,6 @@ class ElementSet {
friend class SliceTraits;
};

namespace detail {

template <class To, class From>
inline std::vector<To> convertSizeVector(const std::vector<From>& from) {
std::vector<To> to(from.size());
std::copy(from.cbegin(), from.cend(), to.begin());

return to;
}
} // namespace detail

inline std::vector<hsize_t> toHDF5SizeVector(const std::vector<size_t>& from) {
return detail::convertSizeVector<hsize_t>(from);
}
Expand Down
31 changes: 31 additions & 0 deletions include/highfive/bits/convert_size_vector.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c), 2017, Adrien Devresse <[email protected]>
* 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 <vector>

namespace HighFive {
namespace detail {

template <class To, class From, class It = From const*>
inline std::vector<To> convertSizeVector(const It& begin, const It& end) {
std::vector<To> to(static_cast<size_t>(end - begin));
std::copy(begin, end, to.begin());

return to;
}

template <class To, class From>
inline std::vector<To> convertSizeVector(const std::vector<From>& from) {
return convertSizeVector<To, From>(from.cbegin(), from.cend());
}

} // namespace detail
} // namespace HighFive
149 changes: 149 additions & 0 deletions include/highfive/experimental/opencv.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#pragma once

#include "../bits/H5Inspector_decl.hpp"
#include "../H5Exception.hpp"

#include <opencv2/opencv.hpp>

#include "../bits/convert_size_vector.hpp"

namespace HighFive {
namespace details {


template <class T>
struct inspector<cv::Mat_<T>> {
using type = cv::Mat_<T>;
using value_type = T;
using base_type = typename inspector<value_type>::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<size_t>(type.step[i]) != ld) {
throw DataSetException("Padded cv::Mat_ are not supported.");
}

ld *= static_cast<size_t>(type.size[i]);
}
}


static constexpr size_t min_ndim = 2 + inspector<value_type>::min_ndim;
static constexpr size_t max_ndim = 1024 + inspector<value_type>::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_type>::value &&
inspector<value_type>::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<size_t>(val.dims) +
inspector<value_type>::getRank(getAnyElement(val));
}
}

static const T& getAnyElement(const type& val) {
return *reinterpret_cast<T const*>(val.data);
}

static T& getAnyElement(type& val) {
return *reinterpret_cast<T*>(val.data);
}

static size_t getLocalRank(const type& val) {
return static_cast<size_t>(val.dims);
}

static std::vector<size_t> getDimensions(const type& val) {
auto local_rank = getLocalRank(val);
auto rank = getRank(val);
std::vector<size_t> dims(rank, 1ul);

if (val.empty()) {
dims[0] = 0ul;
dims[1] = 1ul;
return dims;

Check warning on line 76 in include/highfive/experimental/opencv.hpp

View check run for this annotation

Codecov / codecov/patch

include/highfive/experimental/opencv.hpp#L74-L76

Added lines #L74 - L76 were not covered by tests
}

for (size_t i = 0; i < local_rank; ++i) {
dims[i] = static_cast<size_t>(val.size[static_cast<int>(i)]);
}

auto s = inspector<value_type>::getDimensions(getAnyElement(val));
std::copy(s.cbegin(), s.cend(), dims.begin() + static_cast<int>(local_rank));
return dims;
}

static void prepare(type& val, const std::vector<size_t>& dims) {
auto subdims = detail::convertSizeVector<int>(dims);
val.create(static_cast<int>(subdims.size()), subdims.data());
}

static hdf5_type* data(type& val) {
assert_row_major(val);

if (!is_trivially_copyable) {
throw DataSetException("Invalid used of `inspector<Eigen::Matrix<...>>::data`.");
}

if (val.empty()) {
return nullptr;
}

return inspector<value_type>::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<Eigen::Matrix<...>>::data`.");
}

if (val.empty()) {
return nullptr;
}

return inspector<value_type>::data(getAnyElement(val));
}

static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) {
if (val.empty()) {
return;

Check warning on line 123 in include/highfive/experimental/opencv.hpp

View check run for this annotation

Codecov / codecov/patch

include/highfive/experimental/opencv.hpp#L123

Added line #L123 was not covered by tests
}

auto local_rank = val.dims;
auto subdims = std::vector<size_t>(dims.begin() + local_rank, dims.end());
auto subsize = compute_total_size(subdims);
for (auto it = val.begin(); it != val.end(); ++it) {
inspector<value_type>::serialize(*it, subdims, m);
m += subsize;
}
}

static void unserialize(const hdf5_type* vec_align,
const std::vector<size_t>& dims,
type& val) {
auto local_rank = val.dims;
auto subdims = std::vector<size_t>(dims.begin() + local_rank, dims.end());
auto subsize = compute_total_size(subdims);
for (auto it = val.begin(); it != val.end(); ++it) {
inspector<value_type>::unserialize(vec_align, subdims, *it);
vec_align += subsize;
}
}
};

} // namespace details
} // namespace HighFive
6 changes: 5 additions & 1 deletion tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -63,6 +63,10 @@ if(HIGHFIVE_TEST_SINGLE_INCLUDES)
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")
Expand Down
59 changes: 59 additions & 0 deletions tests/unit/test_opencv.cpp
Original file line number Diff line number Diff line change
@@ -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 <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>

#include <highfive/highfive.hpp>
#include <highfive/experimental/opencv.hpp>
#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_<double>(3, 5);
auto dset = file.createDataSet("a", a);
auto b = dset.read<cv::Mat_<double>>();
REQUIRE(a(0, 0) == b(0, 0));

auto va = std::vector<cv::Mat_<double>>(7, cv::Mat_<double>(3, 5));
auto vdset = file.createDataSet("va", va);
auto vb = vdset.read<std::vector<cv::Mat_<double>>>();
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_<double>(3, 13);

SECTION("write") {
auto sa = cv::Mat_<double>(a.colRange(1, 4));
REQUIRE_THROWS(file.createDataSet("a", sa));
}

SECTION("read") {
auto b = cv::Mat_<double>(3, 17);
auto sb = cv::Mat_<double>(a.colRange(0, 13));
auto dset = file.createDataSet("a", a);

// Creates a new `Mat_` in `sb`.
dset.read(sb);
}
}

#endif

0 comments on commit 5e31ec9

Please sign in to comment.