31#include <opencv2/core.hpp>
32#include <opencv2/imgproc.hpp>
34#include <tl/expected.hpp>
75 using Shape = std::vector<int64_t>;
132 case TensorType::Float:
134 case TensorType::Int64:
136 case TensorType::UInt8:
148 template <
typename T>
153 template <
typename U, std::
size_t N>
156 using value_type = U;
157 static constexpr size_t size = N;
163 template <
typename T>
169 template <
typename Func,
typename InputElem>
172 using return_type = std::decay_t<std::invoke_result_t<Func, InputElem>>;
203 using TensorData = std::variant<
205 std::vector<int64_t>,
206 std::vector<uint8_t>>;
215 static tl::expected<Tensor, NeuralError>
fromCvMat(
const cv::Mat &mat);
246 static tl::expected<Tensor, NeuralError>
fromCvMat(
const cv::Mat &mat,
const Shape &
shape,
float scale = 1.0f, std::vector<float> mean = {}, std::vector<float> std = {});
268 static tl::expected<Tensor, NeuralError>
fromCvMats(
const std::vector<cv::Mat> &mats);
293 static tl::expected<Tensor, NeuralError>
fromCvMats(
const std::vector<cv::Mat> &mats,
const Shape &
shape,
float scale = 1.0f, std::vector<float> mean = {}, std::vector<float> std = {});
309 template <
typename T>
339 template <
typename T>
344 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Data size does not match shape"));
349 t.m_data = std::move(data);
382 template <
typename T>
385 if (std::holds_alternative<std::vector<T>>(m_data))
387 return std::get<std::vector<T>>(m_data);
389 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Tensor does not hold requested data type"));
398 tl::expected<cv::Mat, NeuralError>
toCvMat()
const;
420 tl::expected<void, NeuralError>
reshape(
const Shape &newShape);
448 return std::visit([](
const auto &vec) ->
TensorType
450 using T =
typename std::decay_t<
decltype(vec)>::value_type;
451 if constexpr (std::is_same_v<T, float>) {
452 return TensorType::Float;
453 }
else if constexpr (std::is_same_v<T, int64_t>) {
454 return TensorType::Int64;
455 }
else if constexpr (std::is_same_v<T, uint8_t>) {
456 return TensorType::UInt8;
458 return TensorType::Invalid;
482 template <
typename Op>
483 tl::expected<Tensor, NeuralError>
reduce(
const Op &op, uint64_t axis)
const
485 if (axis >= m_shape.size())
487 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Invalid reduction axis"));
490 return std::visit([&](
const auto &inputVec) -> tl::expected<Tensor, NeuralError>
492 using T =
typename std::decay_t<
decltype(inputVec)>::value_type;
493 const int64_t D = m_shape.size();
495 Shape outShape = m_shape;
501 int64_t dimSize = m_shape[axis];
502 int64_t outerStride = innerStride * dimSize;
504 for (int64_t offset = 0; offset < inputVec.size(); offset += outerStride) {
505 for (int64_t i = 0; i < innerStride; ++i) {
507 int64_t outIdx = (offset / outerStride) * innerStride + i;
509 T acc = op.template initial<T>();
510 for (int64_t d = 0; d < dimSize; ++d) {
511 int64_t idx = offset + d * innerStride + i;
512 op(acc, inputVec[idx]);
514 output[outIdx] = acc;
550 template <
typename Op>
553 if (axis >= m_shape.size())
555 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Invalid reduction axis"));
558 return std::visit([&](
const auto &inputVec) -> tl::expected<Tensor, NeuralError>
560 using T =
typename std::decay_t<
decltype(inputVec)>::value_type;
562 Shape outShape = m_shape;
568 int64_t dimSize = m_shape[axis];
569 int64_t outerStride = innerStride * dimSize;
571 for (int64_t offset = 0; offset < inputVec.size(); offset += outerStride) {
572 for (int64_t i = 0; i < innerStride; ++i) {
573 int64_t outIdx = (offset / outerStride) * innerStride + i;
574 auto acc = op.template initial<T>();
575 for (
size_t d = 0; d < dimSize; ++d) {
576 size_t idx = offset + d * innerStride + i;
577 op(acc, inputVec[idx], d);
579 output[outIdx] =
static_cast<int64_t
>(acc.first);
614 template <
typename Func>
615 tl::expected<Tensor, NeuralError>
map(Func &&f)
const
617 return std::visit([&](
const auto &inputVec) -> tl::expected<Tensor, NeuralError>
619 using T =
typename std::decay_t<
decltype(inputVec)>::value_type;
620 using V = std::decay_t<decltype(f(std::declval<T>()))>;
622 std::vector<V> output;
623 output.resize(inputVec.size());
625 std::transform(inputVec.begin(), inputVec.end(), output.begin(),
626 [&](
const T &val) -> V
665 template <
typename Func>
666 tl::expected<Tensor, NeuralError>
map(Func &&func,
int axis)
const
669 if (axis < 0 || axis >
static_cast<int>(m_shape.size()))
671 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Invalid axis to insert new dimension."));
674 return std::visit([&](
const auto &input) -> tl::expected<Tensor, NeuralError>
676 using T =
typename std::decay_t<
decltype(input)>::value_type;
678 using U =
typename Traits::value_type;
679 constexpr size_t N = Traits::size;
682 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Tensor data is empty."));
686 Shape newShape = m_shape;
687 newShape.insert(newShape.begin() + axis, N);
690 const int64_t newSize = oldSize * N;
692 std::vector<U> outData(newSize);
695 std::vector<int64_t> oldStrides = computeStrides(m_shape);
696 std::vector<int64_t> newStrides = computeStrides(newShape);
700 for (int64_t idx = 0; idx < oldSize; ++idx) {
702 std::vector<int64_t> coord = unravelIndex(idx, oldStrides);
705 auto mapped = func(input[idx]);
708 coord.insert(coord.begin() + axis, 0);
709 for (int64_t i = 0; i < static_cast<int64_t>(N); ++i) {
711 int64_t flatIdx = ravelIndex(coord, newStrides);
712 outData[flatIdx] = mapped[i];
724 tl::expected<void, NeuralError>
squeeze();
731 tl::expected<void, NeuralError>
squeeze(int64_t axis);
738 tl::expected<void, NeuralError>
unsqueeze(int64_t axis);
744 static inline std::vector<int64_t> computeStrides(
const Shape &
shape)
746 std::vector<int64_t> strides(
shape.size(), 1);
747 for (
int i =
shape.size() - 2; i >= 0; --i)
748 strides[i] = strides[i + 1] *
shape[i + 1];
752 static inline std::vector<int64_t> unravelIndex(int64_t index,
const std::vector<int64_t> &strides)
754 std::vector<int64_t> coords(strides.size());
755 for (
size_t i = 0; i < strides.size(); ++i)
757 coords[i] = index / strides[i];
763 static inline int64_t ravelIndex(
const std::vector<int64_t> &coords,
const std::vector<int64_t> &strides)
766 for (
size_t i = 0; i < coords.size(); ++i)
767 index += coords[i] * strides[i];
779 template <
typename T,
int CV_TYPE>
780 static tl::expected<Tensor, NeuralError>
fromCvMatsTyped(
const std::vector<cv::Mat> &mats)
784 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Input vector of cv::Mat is empty"));
787 int channels = mats[0].channels();
788 int height = mats[0].rows;
789 int width = mats[0].cols;
791 Shape shape = {
static_cast<int64_t
>(mats.size()),
static_cast<int64_t
>(channels),
static_cast<int64_t
>(height),
static_cast<int64_t
>(width)};
795 data.reserve(totalSize);
797 for (
const auto &mat : mats)
799 if (mat.depth() != CV_TYPE)
801 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"All cv::Mat must have the same data type"));
804 if (mat.channels() != channels || mat.rows != height || mat.cols != width)
806 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"All cv::Mat must have the same size and number of channels"));
809 std::vector<cv::Mat> splitted;
810 cv::split(mat, splitted);
812 for (
int c = 0; c < channels; ++c)
815 const T *channelData = splitted[c].ptr<T>();
816 data.insert(data.end(), channelData, channelData + height * width);
829 const cv::Mat *matPtr = &mat;
832 if (mat.channels() == 3)
834 cv::cvtColor(*matPtr, tmp, cv::COLOR_BGR2RGB);
840 if (size != cv::Size(-1,-1) && size != matPtr->size())
842 cv::resize(*matPtr, tmp, size, 0, 0, cv::INTER_AREA);
847 matPtr->convertTo(tmp, CV_32F, scale);
854 static cv::Mat
preprocessCvMat(
const cv::Mat &mat,
const Shape &
shape,
float scale,
const std::vector<float> &mean,
const std::vector<float> &std)
859 assert(mean.size() == tmp.channels());
860 assert(std.size() == tmp.channels());
862 assert(std::all_of(std.begin(), std.end(), [](
float s) { return s != 0.0f; }));
865 int channels = tmp.channels();
867 int cols = tmp.cols * channels;
869 if (tmp.isContinuous())
875 for (
int i = 0; i < rows; ++i)
877 float *ptr = tmp.ptr<
float>(i);
878 for (
int j = 0; j < cols; j += channels)
880 for (
int c = 0; c < channels; ++c)
882 ptr[j + c] = (ptr[j + c] - mean[c]) / std[c];
894#ifndef TENSOR_DEBUG_PRINT
896#define TENSOR_DEBUG_PRINT(tensor) ((void)0);
899#define TENSOR_DEBUG_PRINT(tensor) (std::cout << (tensor).toString() << std::endl);
903#ifndef SHAPE_DEBUG_PRINT
905#define SHAPE_DEBUG_PRINT(shape) ((void)0);
908#define SHAPE_DEBUG_PRINT(shape) (std::cout << NN::shapeToString(shape) << std::endl);
912#ifndef RETURN_ON_ERROR
913#define RETURN_ON_ERROR(expr, retVal) \
916 auto _res = (expr); \
919 std::cerr << "Error: " << _res.error() << std::endl; \
Defines error handling classes for the neural network module.
Contains reduction operations for tensors such as sum, min, max, argmin, and argmax.
Represents an error that occurred in the neural network module and contains the error type and messag...
Definition NeuralError.h:48
A class that implements the NeuralNet interface using ONNX Runtime.
Definition OrtNeuralNet.h:58
A Tensor represents a N-dimensional array containing elements of the same type. Can be used as input ...
Definition Tensor.h:201
static tl::expected< Tensor, NeuralError > fromCvMats(const std::vector< cv::Mat > &mats)
Create a new Tensor object from a vector of cv::Mat objects. The cv::Mat objects must have the same s...
Definition Tensor.cpp:158
tl::expected< Tensor, NeuralError > reduceWithIndex(const Op &op, uint64_t axis) const
Reduce the Tensor along a given axis by applying an accumulative operation. The dimension in the rduc...
Definition Tensor.h:551
const Shape & shape() const
Readonly access to the shape of the tensor. Valid tensors ensure the shape is static,...
Definition Tensor.h:405
bool empty() const
Check if the tensor is empty, so to say it contains no elements.
Definition Tensor.h:435
tl::expected< Tensor, NeuralError > reduce(const Op &op, uint64_t axis) const
Reduce the Tensor along a given axis by applying an accumulative operation.
Definition Tensor.h:483
static cv::Mat preprocessCvMat(const cv::Mat &mat, const Shape &shape, float scale)
Preprocess a cv::Mat to match the given shape by resizing, color conversion from BGR to RGB,...
Definition Tensor.h:826
static tl::expected< Tensor, NeuralError > fromData(const std::vector< T > &data, const Shape &shape)
Create a new Tensor object from a given data vector and shape. The number of elements in the vector m...
Definition Tensor.h:310
static cv::Mat preprocessCvMat(const cv::Mat &mat, const Shape &shape, float scale, const std::vector< float > &mean, const std::vector< float > &std)
Preprocess a cv::Mat to match the given shape by resizing, color conversion from BGR to RGB,...
Definition Tensor.h:854
tl::expected< void, NeuralError > squeeze()
Squeeze the Tensor by removing dimensions of size 1. This operation is performed inplace.
Definition Tensor.cpp:388
tl::expected< cv::Mat, NeuralError > toCvMat() const
Create a cv::Mat from a Tensor. In case of 2/3 dimensions this will convert back to CVs HWC layout....
Definition Tensor.cpp:249
TensorType dtype() const
Check the data type of the Tensor. This is deduced from the data type of the contained elements.
Definition Tensor.h:446
static tl::expected< Tensor, NeuralError > fromCvMat(const cv::Mat &mat)
Create a new Tensor object from a cv::Mat. This will convert from CVs HWC layout to ONNX standard lay...
Definition Tensor.cpp:6
tl::expected< std::vector< T >, NeuralError > toVector() const
Create a vector containing the data from the Tensor.
Definition Tensor.h:383
std::string toString() const
Create a human-readable string representation containing the Tensors shape and data type.
Definition Tensor.cpp:232
static tl::expected< Tensor, NeuralError > fromData(std::vector< T > &&data, const Shape &shape)
Create a new Tensor object from a given data vector and shape. The number of elements in the vector m...
Definition Tensor.h:340
tl::expected< Tensor, NeuralError > map(Func &&func, int axis) const
Map each element of the Tensor to an array of new values by applying a given function element-wise....
Definition Tensor.h:666
tl::expected< Tensor, NeuralError > map(Func &&f) const
Map each element of the Tensor to a new value by applying a given function element-wise....
Definition Tensor.h:615
tl::expected< void, NeuralError > unsqueeze(int64_t axis)
Add a new dimension of size 1 at the specified axis.
Definition Tensor.cpp:406
int64_t numElements() const
Returns the number of elements contained in the Tensor.
Definition Tensor.cpp:355
static tl::expected< Tensor, NeuralError > fromCvMatsTyped(const std::vector< cv::Mat > &mats)
Create a new Tensor object from a vector of cv::Mat objects with a specific type.
Definition Tensor.h:780
tl::expected< void, NeuralError > reshape(const Shape &newShape)
Reshape will interprete the data elements contained in the Tensor as a different shape....
Definition Tensor.cpp:325
TensorType
TensorType encapsulates the supported data types of tensor elements. The supported types are:
Definition Tensor.h:115
std::vector< int64_t > Shape
Shape of a N-dimensional Tensor represented as the size in each dimension. Can be -1 in case of dynam...
Definition Tensor.h:75
NN Neural Network Library containing Tensor and NeuralNet classes for inference.
Definition NeuralError.h:13
int64_t shapeNumElements(const Shape &shape)
Calculates the number of elements from a given Shape.
Definition Tensor.cpp:360
constexpr const char * toString(TensorType type)
Convert the TensorType to a human-readable string.
Definition Tensor.h:128
typename std::decay< T >::type decay_t
Remove cv/ref qualifiers and decay to check std::array<T, N>
Definition Tensor.h:164
std::string shapeToString(const Shape &shape)
Creates a human-readable string from the given shape.
Definition Tensor.cpp:365
int64_t shapeToStride(const Shape &shape, uint64_t axis)
Calculates the stride to iterate elements in a given axis.
Definition Tensor.cpp:379
Checks if a type is a std::array.
Definition Tensor.h:150
Traits for mapping a function over a std::array.
Definition Tensor.h:171