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);
247 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 = {},
int gridSize = 1);
269 static tl::expected<Tensor, NeuralError>
fromCvMats(
const std::vector<cv::Mat> &mats);
296 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 = {},
int gridSize = 1);
312 template <
typename T>
342 template <
typename T>
347 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Data size does not match shape"));
352 t.m_data = std::move(data);
385 template <
typename T>
388 if (std::holds_alternative<std::vector<T>>(m_data))
390 return std::get<std::vector<T>>(m_data);
392 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Tensor does not hold requested data type"));
401 tl::expected<cv::Mat, NeuralError>
toCvMat()
const;
423 tl::expected<void, NeuralError>
reshape(
const Shape &newShape);
451 return std::visit([](
const auto &vec) ->
TensorType
453 using T =
typename std::decay_t<
decltype(vec)>::value_type;
454 if constexpr (std::is_same_v<T, float>) {
455 return TensorType::Float;
456 }
else if constexpr (std::is_same_v<T, int64_t>) {
457 return TensorType::Int64;
458 }
else if constexpr (std::is_same_v<T, uint8_t>) {
459 return TensorType::UInt8;
461 return TensorType::Invalid;
485 template <
typename Op>
486 tl::expected<Tensor, NeuralError>
reduce(
const Op &op, uint64_t axis)
const
488 if (axis >= m_shape.size())
490 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Invalid reduction axis"));
493 return std::visit([&](
const auto &inputVec) -> tl::expected<Tensor, NeuralError>
495 using T =
typename std::decay_t<
decltype(inputVec)>::value_type;
496 const int64_t D = m_shape.size();
498 Shape outShape = m_shape;
504 int64_t dimSize = m_shape[axis];
505 int64_t outerStride = innerStride * dimSize;
507 for (int64_t offset = 0; offset < inputVec.size(); offset += outerStride) {
508 for (int64_t i = 0; i < innerStride; ++i) {
510 int64_t outIdx = (offset / outerStride) * innerStride + i;
512 T acc = op.template initial<T>();
513 for (int64_t d = 0; d < dimSize; ++d) {
514 int64_t idx = offset + d * innerStride + i;
515 op(acc, inputVec[idx]);
517 output[outIdx] = acc;
553 template <
typename Op>
556 if (axis >= m_shape.size())
558 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Invalid reduction axis"));
561 return std::visit([&](
const auto &inputVec) -> tl::expected<Tensor, NeuralError>
563 using T =
typename std::decay_t<
decltype(inputVec)>::value_type;
565 Shape outShape = m_shape;
571 int64_t dimSize = m_shape[axis];
572 int64_t outerStride = innerStride * dimSize;
574 for (int64_t offset = 0; offset < inputVec.size(); offset += outerStride) {
575 for (int64_t i = 0; i < innerStride; ++i) {
576 int64_t outIdx = (offset / outerStride) * innerStride + i;
577 auto acc = op.template initial<T>();
578 for (
size_t d = 0; d < dimSize; ++d) {
579 size_t idx = offset + d * innerStride + i;
580 op(acc, inputVec[idx], d);
582 output[outIdx] =
static_cast<int64_t
>(acc.first);
617 template <
typename Func>
618 tl::expected<Tensor, NeuralError>
map(Func &&f)
const
620 return std::visit([&](
const auto &inputVec) -> tl::expected<Tensor, NeuralError>
622 using T =
typename std::decay_t<
decltype(inputVec)>::value_type;
623 using V = std::decay_t<decltype(f(std::declval<T>()))>;
625 std::vector<V> output;
626 output.resize(inputVec.size());
628 std::transform(inputVec.begin(), inputVec.end(), output.begin(),
629 [&](
const T &val) -> V
668 template <
typename Func>
669 tl::expected<Tensor, NeuralError>
map(Func &&func,
int axis)
const
672 if (axis < 0 || axis >
static_cast<int>(m_shape.size()))
674 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Invalid axis to insert new dimension."));
677 return std::visit([&](
const auto &input) -> tl::expected<Tensor, NeuralError>
679 using T =
typename std::decay_t<
decltype(input)>::value_type;
681 using U =
typename Traits::value_type;
682 constexpr size_t N = Traits::size;
685 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Tensor data is empty."));
689 Shape newShape = m_shape;
690 newShape.insert(newShape.begin() + axis, N);
693 const int64_t newSize = oldSize * N;
695 std::vector<U> outData(newSize);
698 std::vector<int64_t> oldStrides = computeStrides(m_shape);
699 std::vector<int64_t> newStrides = computeStrides(newShape);
703 for (int64_t idx = 0; idx < oldSize; ++idx) {
705 std::vector<int64_t> coord = unravelIndex(idx, oldStrides);
708 auto mapped = func(input[idx]);
711 coord.insert(coord.begin() + axis, 0);
712 for (int64_t i = 0; i < static_cast<int64_t>(N); ++i) {
714 int64_t flatIdx = ravelIndex(coord, newStrides);
715 outData[flatIdx] = mapped[i];
727 tl::expected<void, NeuralError>
squeeze();
734 tl::expected<void, NeuralError>
squeeze(int64_t axis);
741 tl::expected<void, NeuralError>
unsqueeze(int64_t axis);
747 static inline std::vector<int64_t> computeStrides(
const Shape &
shape)
749 std::vector<int64_t> strides(
shape.size(), 1);
750 for (
int i =
shape.size() - 2; i >= 0; --i)
751 strides[i] = strides[i + 1] *
shape[i + 1];
755 static inline std::vector<int64_t> unravelIndex(int64_t index,
const std::vector<int64_t> &strides)
757 std::vector<int64_t> coords(strides.size());
758 for (
size_t i = 0; i < strides.size(); ++i)
760 coords[i] = index / strides[i];
766 static inline int64_t ravelIndex(
const std::vector<int64_t> &coords,
const std::vector<int64_t> &strides)
769 for (
size_t i = 0; i < coords.size(); ++i)
770 index += coords[i] * strides[i];
782 template <
typename T,
int CV_TYPE>
783 static tl::expected<Tensor, NeuralError>
fromCvMatsTyped(
const std::vector<cv::Mat> &mats)
787 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"Input vector of cv::Mat is empty"));
790 int channels = mats[0].channels();
791 int height = mats[0].rows;
792 int width = mats[0].cols;
794 Shape shape = {
static_cast<int64_t
>(mats.size()),
static_cast<int64_t
>(channels),
static_cast<int64_t
>(height),
static_cast<int64_t
>(width)};
798 data.reserve(totalSize);
800 for (
const auto &mat : mats)
802 if (mat.depth() != CV_TYPE)
804 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"All cv::Mat must have the same data type"));
807 if (mat.channels() != channels || mat.rows != height || mat.cols != width)
809 return tl::unexpected(
NeuralError(ErrorCode::InvalidArgument,
"All cv::Mat must have the same size and number of channels"));
812 std::vector<cv::Mat> splitted;
813 cv::split(mat, splitted);
815 for (
int c = 0; c < channels; ++c)
818 const T *channelData = splitted[c].ptr<T>();
819 data.insert(data.end(), channelData, channelData + height * width);
832 const cv::Mat *matPtr = &mat;
835 if (mat.channels() == 3)
837 cv::cvtColor(*matPtr, tmp, cv::COLOR_BGR2RGB);
845 if (size == cv::Size(-1,-1)) {
849 ((matPtr->cols + gridSize - 1) / gridSize) * gridSize,
850 ((matPtr->rows + gridSize - 1) / gridSize) * gridSize);
851 cv::resize(*matPtr, tmp, newSize, 0, 0, cv::INTER_AREA);
857 if (size != matPtr->size())
859 cv::resize(*matPtr, tmp, size, 0, 0, cv::INTER_AREA);
866 matPtr->convertTo(tmp, CV_32F, scale);
873 static cv::Mat
preprocessCvMat(
const cv::Mat &mat,
const Shape &
shape,
float scale,
const std::vector<float> &mean,
const std::vector<float> &std,
int gridSize)
878 assert(mean.size() == tmp.channels());
879 assert(std.size() == tmp.channels());
881 assert(std::all_of(std.begin(), std.end(), [](
float s) { return s != 0.0f; }));
884 int channels = tmp.channels();
886 int cols = tmp.cols * channels;
888 if (tmp.isContinuous())
894 for (
int i = 0; i < rows; ++i)
896 float *ptr = tmp.ptr<
float>(i);
897 for (
int j = 0; j < cols; j += channels)
899 for (
int c = 0; c < channels; ++c)
901 ptr[j + c] = (ptr[j + c] - mean[c]) / std[c];
913#ifndef TENSOR_DEBUG_PRINT
915#define TENSOR_DEBUG_PRINT(tensor) ((void)0);
918#define TENSOR_DEBUG_PRINT(tensor) (std::cout << (tensor).toString() << std::endl);
922#ifndef SHAPE_DEBUG_PRINT
924#define SHAPE_DEBUG_PRINT(shape) ((void)0);
927#define SHAPE_DEBUG_PRINT(shape) (std::cout << NN::shapeToString(shape) << std::endl);
931#ifndef RETURN_ON_ERROR
932#define RETURN_ON_ERROR(expr, retVal) \
935 auto _res = (expr); \
938 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:164
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:554
const Shape & shape() const
Readonly access to the shape of the tensor. Valid tensors ensure the shape is static,...
Definition Tensor.h:408
static cv::Mat preprocessCvMat(const cv::Mat &mat, const Shape &shape, float scale, int gridSize)
Preprocess a cv::Mat to match the given shape by resizing, color conversion from BGR to RGB,...
Definition Tensor.h:829
bool empty() const
Check if the tensor is empty, so to say it contains no elements.
Definition Tensor.h:438
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:486
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:313
tl::expected< void, NeuralError > squeeze()
Squeeze the Tensor by removing dimensions of size 1. This operation is performed inplace.
Definition Tensor.cpp:394
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:255
static cv::Mat preprocessCvMat(const cv::Mat &mat, const Shape &shape, float scale, const std::vector< float > &mean, const std::vector< float > &std, int gridSize)
Preprocess a cv::Mat to match the given shape by resizing, color conversion from BGR to RGB,...
Definition Tensor.h:873
TensorType dtype() const
Check the data type of the Tensor. This is deduced from the data type of the contained elements.
Definition Tensor.h:449
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:386
std::string toString() const
Create a human-readable string representation containing the Tensors shape and data type.
Definition Tensor.cpp:238
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:343
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:669
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:618
tl::expected< void, NeuralError > unsqueeze(int64_t axis)
Add a new dimension of size 1 at the specified axis.
Definition Tensor.cpp:412
int64_t numElements() const
Returns the number of elements contained in the Tensor.
Definition Tensor.cpp:361
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:783
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:331
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:366
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:371
int64_t shapeToStride(const Shape &shape, uint64_t axis)
Calculates the stride to iterate elements in a given axis.
Definition Tensor.cpp:385
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