Skip to content

Commit 6dc4bf9

Browse files
authored
Merge pull request #89 from ami-iit/add_slices_eigen
Added possibility to select slice when converting MultiDimensionalArray to an EigenMatrix
2 parents 1a846dc + 747dc32 commit 6dc4bf9

File tree

6 files changed

+239
-5
lines changed

6 files changed

+239
-5
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
workflow_dispatch:
77

88
env:
9-
Catch2_TAG: v3.0.1
9+
Catch2_TAG: v3.7.1
1010

1111
jobs:
1212
build:

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# BSD-2-Clause license (https://opensource.org/licenses/BSD-2-Clause).
44

55
cmake_minimum_required(VERSION 3.10)
6-
project(matioCpp VERSION 0.2.6 LANGUAGES CXX)
6+
project(matioCpp VERSION 0.3.0 LANGUAGES CXX)
77

88
# Defines the CMAKE_INSTALL_LIBDIR, CMAKE_INSTALL_BINDIR and many other useful macros.
99
# See https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html

README.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ It can be used for reading and writing binary MATLAB `.mat` files from C++, with
1717

1818
The depencies are [``CMake``](https://cmake.org/) (minimum version 3.10) and [``matio``](https://github.com/tbeu/matio). While we suggest to follow the build instructions provided in the [``matio`` home page](https://github.com/tbeu/matio), it can also installed from common package managers:
1919
- Linux: ``sudo apt install libmatio-dev``
20-
- Linux, macOS, Windows, via [``conda-forge``](https://conda-forge.org/): ``mamba install -c conda-forge libmatio``
20+
- Linux, macOS, Windows, via [``conda-forge``](https://conda-forge.org/): ``conda install -c conda-forge libmatio``
2121

2222
[`Eigen`](https://eigen.tuxfamily.org/index.php) is an optional dependency. If available, some conversions are defined.
2323

@@ -153,6 +153,44 @@ Eigen::Vector3i eigenVec;
153153
eigenVec << 2, 4, 6;
154154
auto toMatioEigenVec = matioCpp::make_variable("testEigen", eigenVec);
155155
```
156+
It is also possible to slice a ``MultiDimensionalArray`` into an Eigen matrix:
157+
```c++
158+
std::vector<float> tensor(12);
159+
for (size_t i = 0; i < 12; ++i)
160+
{
161+
tensor[i] = i + 1.0;
162+
}
163+
164+
matioCpp::MultiDimensionalArray<float> matioCppMatrix2("matrix", { 2, 2, 3 }, tensor.data());
165+
166+
/*
167+
So we have a tensor of the type
168+
| 1 3 | | 5 7 | | 9 11 |
169+
| 2 4 | | 6 8 | | 10 12 |
170+
*/
171+
172+
Eigen::MatrixXf slice1 = matioCpp::to_eigen(matioCppMatrix2, { -1, -1, 0 }; //Equivalent to the Matlab operation matioCppMatrix2(:,:,1)
173+
/*
174+
Obtain
175+
| 1 3 |
176+
| 2 4 |
177+
*/
178+
179+
Eigen::MatrixXf slice2 = matioCpp::to_eigen(matioCppMatrix2, { 1, -1, -1 }; //Equivalent to the Matlab operation matioCppMatrix2(2,:,:)
180+
/*
181+
Obtain
182+
| 2 6 10|
183+
| 4 8 12|
184+
*/
185+
186+
Eigen::MatrixXf slice3 = matioCpp::to_eigen(matioCppMatrix2, { -1, 0, 0 }; //Equivalent to the Matlab operation matioCppMatrix2(:,1,1)
187+
/*
188+
Obtain
189+
| 1 |
190+
| 2 |
191+
*/
192+
```
193+
In the slice, the value `-1` means that the entire dimension is taken.
156194
157195
``matioCpp`` also exploits [``visit_struct``](https://github.com/garbageslam/visit_struct) to parse C++ structs into ``matioCpp`` structs. Example:
158196
```c++

include/matioCpp/EigenConversions.h

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
namespace matioCpp
2121
{
2222

23+
template <typename type>
24+
using EigenMapWithStride = Eigen::Map<Eigen::Matrix<type, Eigen::Dynamic, Eigen::Dynamic>, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>>;
25+
26+
template <typename type>
27+
using ConstEigenMapWithStride = Eigen::Map<const Eigen::Matrix<type, Eigen::Dynamic, Eigen::Dynamic>, 0, Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>>;
28+
2329
/**
2430
* @brief Conversion from a MultiDimensionalArray to an Eigen matrix
2531
* @param input The MultiDimensionalArray
@@ -34,7 +40,31 @@ inline Eigen::Map<Eigen::Matrix<type, Eigen::Dynamic, Eigen::Dynamic>> to_eigen(
3440
* @return A const map from the internal data of the MultiDimensionalArray
3541
*/
3642
template <typename type>
37-
inline const Eigen::Map<Eigen::Matrix<type, Eigen::Dynamic, Eigen::Dynamic>> to_eigen(const MultiDimensionalArray<type>& input);
43+
inline Eigen::Map<const Eigen::Matrix<type, Eigen::Dynamic, Eigen::Dynamic>> to_eigen(const MultiDimensionalArray<type>& input);
44+
45+
/**
46+
* @brief Conversion from a MultiDimensionalArray to an Eigen matrix
47+
* @param input The MultiDimensionalArray
48+
* @param slice The slice to extract from the MultiDimensionalArray. Use a negative value to extract the whole dimension.
49+
* If not provided, it is assumed that the input is a matrix.
50+
* At most 2 slices are allowed, one for the rows and one for the columns of the output matrix.
51+
* If only one dimension is selected, the output is a column vector.
52+
* @return A map from the internal data of the MultiDimensionalArray
53+
*/
54+
template <typename type>
55+
inline EigenMapWithStride<type> to_eigen(MultiDimensionalArray<type>& input, const std::vector<int>& slice);
56+
57+
/**
58+
* @brief Conversion from a const MultiDimensionalArray to an Eigen matrix
59+
* @param input The MultiDimensionalArray
60+
* @param slice The slice to extract from the MultiDimensionalArray. Use a negative value to extract the whole dimension.
61+
* If not provided, it is assumed that the input is a matrix.
62+
* At most 2 slices are allowed, one for the rows and one for the columns of the output matrix.
63+
* If only one dimension is selected, the output is a column vector.
64+
* @return A const map from the internal data of the MultiDimensionalArray
65+
*/
66+
template <typename type>
67+
inline ConstEigenMapWithStride<type> to_eigen(const MultiDimensionalArray<type>& input, const std::vector<int>& slice);
3868

3969
/**
4070
* @brief Conversion from a Vector to an Eigen vector

include/matioCpp/impl/EigenConversions.tpp

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,91 @@
1010

1111
#include <cassert>
1212

13+
namespace matioCpp
14+
{
15+
struct SlicingInfo
16+
{
17+
std::pair<size_t, size_t> dimensions{0,0};
18+
size_t offset{ 0 };
19+
size_t innerStride{ 0 };
20+
size_t outerStride{ 0 };
21+
bool valid{ false };
22+
};
23+
24+
template <typename type>
25+
SlicingInfo computeSlicingInfo(const matioCpp::MultiDimensionalArray<type>& input, const std::vector<int>& slice)
26+
{
27+
std::string errorPrefix = "[ERROR][matioCpp::to_eigen] ";
28+
29+
using index_type = typename matioCpp::MultiDimensionalArray<type>::index_type;
30+
SlicingInfo info;
31+
32+
const auto& dimensions = input.dimensions();
33+
34+
if (slice.size() != dimensions.size())
35+
{
36+
std::cerr << errorPrefix << "The number of slices must be equal to the number of dimensions of the input MultiDimensionalArray" << std::endl;
37+
assert(false);
38+
return info;
39+
}
40+
41+
std::vector<index_type> startingIndex(slice.size(), 0);
42+
index_type previousDimensionsFactorial = 1;
43+
44+
for (size_t i = 0; i < slice.size(); ++i)
45+
{
46+
if (slice[i] >= 0)
47+
{
48+
if (slice[i] >= dimensions(i))
49+
{
50+
std::cerr << errorPrefix << "The slice is larger than the dimension of the input MultiDimensionalArray" << std::endl;
51+
assert(false);
52+
return SlicingInfo();
53+
}
54+
startingIndex[i] = static_cast<size_t>(slice[i]);
55+
}
56+
else
57+
{
58+
if (info.dimensions.first == 0)
59+
{
60+
info.dimensions.first = dimensions(i);
61+
info.innerStride = previousDimensionsFactorial;
62+
}
63+
else if (info.dimensions.second == 0)
64+
{
65+
info.dimensions.second = dimensions(i);
66+
info.outerStride = previousDimensionsFactorial;
67+
}
68+
else
69+
{
70+
std::cerr << errorPrefix << "Only at most two free dimensions are allowed" << std::endl;
71+
assert(false);
72+
return SlicingInfo();
73+
}
74+
}
75+
previousDimensionsFactorial *= dimensions(i);
76+
}
77+
78+
if (info.dimensions.first == 0) //Single element
79+
{
80+
info.dimensions.first = 1;
81+
info.innerStride = 1;
82+
}
83+
84+
if (info.dimensions.second == 0) //Vector case
85+
{
86+
info.dimensions.second = 1;
87+
info.outerStride = info.dimensions.first * info.innerStride;
88+
}
89+
90+
info.offset = input.rawIndexFromIndices(startingIndex);
91+
92+
info.valid = true;
93+
return info;
94+
95+
}
96+
}
97+
1398
template <typename type>
1499
inline Eigen::Map<Eigen::Matrix<type, Eigen::Dynamic, Eigen::Dynamic>> matioCpp::to_eigen(matioCpp::MultiDimensionalArray<type>& input)
15100
{
@@ -19,13 +104,44 @@ inline Eigen::Map<Eigen::Matrix<type, Eigen::Dynamic, Eigen::Dynamic>> matioCpp:
19104
}
20105

21106
template <typename type>
22-
inline const Eigen::Map<Eigen::Matrix<type, Eigen::Dynamic, Eigen::Dynamic>> matioCpp::to_eigen(const matioCpp::MultiDimensionalArray<type>& input)
107+
inline Eigen::Map<const Eigen::Matrix<type, Eigen::Dynamic, Eigen::Dynamic>> matioCpp::to_eigen(const matioCpp::MultiDimensionalArray<type>& input)
23108
{
24109
assert(input.isValid());
25110
assert(input.dimensions().size() == 2);
26111
return Eigen::Map<const Eigen::Matrix<type, Eigen::Dynamic, Eigen::Dynamic>>(input.data(), input.dimensions()(0), input.dimensions()(1));
27112
}
28113

114+
template <typename type>
115+
inline matioCpp::EigenMapWithStride<type> matioCpp::to_eigen(matioCpp::MultiDimensionalArray<type>& input, const std::vector<int>& slice)
116+
{
117+
assert(input.isValid());
118+
119+
auto slicingInfo = computeSlicingInfo(input, slice);
120+
Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic> stride(slicingInfo.outerStride, slicingInfo.innerStride);
121+
if (!slicingInfo.valid)
122+
{
123+
return matioCpp::EigenMapWithStride<type>(nullptr, 0, 0, stride);
124+
}
125+
return matioCpp::EigenMapWithStride<type>(input.data() + slicingInfo.offset, slicingInfo.dimensions.first, slicingInfo.dimensions.second, stride);
126+
}
127+
128+
template <typename type>
129+
inline matioCpp::ConstEigenMapWithStride<type> matioCpp::to_eigen(const matioCpp::MultiDimensionalArray<type>& input, const std::vector<int>& slice)
130+
{
131+
assert(input.isValid());
132+
133+
auto slicingInfo = computeSlicingInfo(input, slice);
134+
Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic> stride(slicingInfo.outerStride, slicingInfo.innerStride);
135+
136+
137+
if (!slicingInfo.valid)
138+
{
139+
return ConstEigenMapWithStride<type>(nullptr, 0, 0, stride);
140+
}
141+
142+
return ConstEigenMapWithStride<type>(input.data() + slicingInfo.offset, slicingInfo.dimensions.first, slicingInfo.dimensions.second, stride);
143+
}
144+
29145
template <typename type>
30146
inline Eigen::Map<Eigen::Matrix<type, Eigen::Dynamic, 1>> matioCpp::to_eigen(matioCpp::Vector<type>& input)
31147
{

test/ExogenousConversionsUnitTest.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,56 @@ TEST_CASE("Eigen Conversions")
8585

8686
Eigen::MatrixXf toEigenMatrix = matioCpp::to_eigen(matioCppMatrix);
8787
checkSameMatrix(toEigenMatrix, matioCppMatrix);
88+
89+
const auto& constMatioCppMatrix = matioCppMatrix;
90+
Eigen::MatrixXf toEigenMatrixConst = matioCpp::to_eigen(constMatioCppMatrix);
91+
checkSameMatrix(toEigenMatrixConst, constMatioCppMatrix);
92+
93+
std::vector<float> tensor(12);
94+
for (size_t i = 0; i < 12; ++i)
95+
{
96+
tensor[i] = i + 1.0;
97+
}
98+
99+
matioCpp::MultiDimensionalArray<float> matioCppMatrix2("matrix", { 2, 2, 3 }, tensor.data());
100+
101+
/*
102+
So we have a tensor of the type
103+
| 1 3 | | 5 7 | | 9 11 |
104+
| 2 4 | | 6 8 | | 10 12 |
105+
*/
106+
107+
Eigen::MatrixXf expectedSlice(2, 2);
108+
expectedSlice << 1.0, 3.0,
109+
2.0, 4.0;
110+
REQUIRE(expectedSlice.isApprox(matioCpp::to_eigen(matioCppMatrix2, { -1, -1, 0 }), 1e-5));
111+
112+
113+
expectedSlice.resize(2,3);
114+
expectedSlice << 2.0, 6.0, 10.0,
115+
4.0, 8.0, 12.0;
116+
REQUIRE(expectedSlice.isApprox(matioCpp::to_eigen(matioCppMatrix2, {1, -1, -1 }), 1e-5));
117+
118+
expectedSlice.resize(2, 3);
119+
expectedSlice << 3.0, 7.0, 11.0,
120+
4.0, 8.0, 12.0;
121+
REQUIRE(expectedSlice.isApprox(matioCpp::to_eigen(matioCppMatrix2, { -1, 1, -1 }), 1e-5));
122+
123+
expectedSlice.resize(2, 1);
124+
expectedSlice << 11.0,
125+
12.0;
126+
REQUIRE(expectedSlice.isApprox(matioCpp::to_eigen(matioCppMatrix2, { -1, 1, 2 }), 1e-5));
127+
128+
expectedSlice.resize(1, 1);
129+
expectedSlice << 8.0;
130+
REQUIRE(expectedSlice.isApprox(matioCpp::to_eigen(matioCppMatrix2, { 1, 1, 1 }), 1e-5));
131+
132+
const auto& constMatioCppMatrix2 = matioCppMatrix2;
133+
expectedSlice.resize(2, 3);
134+
expectedSlice << 1.0, 5.0, 9.0,
135+
3.0, 7.0, 11.0;
136+
REQUIRE(expectedSlice.isApprox(matioCpp::to_eigen(constMatioCppMatrix2, { 0, -1, -1 }), 1e-5));
137+
88138
}
89139

90140
SECTION("From Eigen")

0 commit comments

Comments
 (0)