iVS3D v2.0.0
Loading...
Searching...
No Matches
Tensor.h
Go to the documentation of this file.
1#pragma once
2
19#include <variant>
20#include <vector>
21#include <cstdint>
22#include <string>
23#include <cmath>
24#include <numeric>
25#include <iostream>
26#include <sstream>
27#include <array>
28#include <type_traits>
29#include <utility>
30
31#include <opencv2/core.hpp>
32#include <opencv2/imgproc.hpp>
33
34#include <tl/expected.hpp>
35
36#include <ReduceOps.h>
37
38#include "NeuralError.h"
39
54namespace NN
55{
75 using Shape = std::vector<int64_t>;
76
85 int64_t shapeNumElements(const Shape &shape);
86
94 int64_t shapeToStride(const Shape &shape, uint64_t axis);
95
102 std::string shapeToString(const Shape &shape);
103
114 enum class TensorType
115 {
116 Float, // float32
117 Int64, // int64_t
118 UInt8, // uint8_t
119 Invalid // Invalid type, used for error handling
120 };
121
128 constexpr const char *toString(TensorType type)
129 {
130 switch (type)
131 {
132 case TensorType::Float:
133 return "float32";
134 case TensorType::Int64:
135 return "int64";
136 case TensorType::UInt8:
137 return "uint8";
138 default:
139 return "Invalid";
140 }
141 };
142
143 // --- Helper to extract std::array traits
144
148 template <typename T>
149 struct is_std_array : std::false_type
150 {
151 };
152
153 template <typename U, std::size_t N>
154 struct is_std_array<std::array<U, N>> : std::true_type
155 {
156 using value_type = U;
157 static constexpr size_t size = N;
158 };
159
163 template <typename T>
164 using decay_t = typename std::decay<T>::type;
165
169 template <typename Func, typename InputElem>
171 {
172 using return_type = std::decay_t<std::invoke_result_t<Func, InputElem>>;
173 static_assert(is_std_array<return_type>::value, "Function must return std::array");
174
175 using value_type = typename is_std_array<return_type>::value_type;
176 static constexpr size_t size = is_std_array<return_type>::size;
177 };
178 // --- End heplers
179
200 class Tensor
201 {
202 public:
203 using TensorData = std::variant<
204 std::vector<float>,
205 std::vector<int64_t>,
206 std::vector<uint8_t>>;
207
215 static tl::expected<Tensor, NeuralError> fromCvMat(const cv::Mat &mat);
216
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 = {});
247
268 static tl::expected<Tensor, NeuralError> fromCvMats(const std::vector<cv::Mat> &mats);
269
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 = {});
294
309 template <typename T>
310 static tl::expected<Tensor, NeuralError> fromData(const std::vector<T> &data, const Shape &shape)
311 {
312 return fromData(std::vector<T>(data), shape); // delegates to move overload
313 }
314
339 template <typename T>
340 static tl::expected<Tensor, NeuralError> fromData(std::vector<T> &&data, const Shape &shape)
341 {
342 if (static_cast<size_t>(shapeNumElements(shape)) != data.size())
343 {
344 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Data size does not match shape"));
345 }
346
347 Tensor t;
348 t.m_shape = shape;
349 t.m_data = std::move(data);
350 return t;
351 }
352
382 template <typename T>
383 tl::expected<std::vector<T>, NeuralError> toVector() const
384 {
385 if (std::holds_alternative<std::vector<T>>(m_data))
386 {
387 return std::get<std::vector<T>>(m_data);
388 }
389 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Tensor does not hold requested data type"));
390 }
391
398 tl::expected<cv::Mat, NeuralError> toCvMat() const;
399
405 const Shape &shape() const { return m_shape; }
406
412 std::string toString() const;
413
420 tl::expected<void, NeuralError> reshape(const Shape &newShape);
421
427 int64_t numElements() const;
428
435 bool empty() const
436 {
437 return m_shape.empty() || shapeNumElements(m_shape) == 0;
438 }
439
447 {
448 return std::visit([](const auto &vec) -> TensorType
449 {
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;
457 } else {
458 return TensorType::Invalid; // Unsupported type
459 } }, m_data);
460 };
461
482 template <typename Op>
483 tl::expected<Tensor, NeuralError> reduce(const Op &op, uint64_t axis) const
484 {
485 if (axis >= m_shape.size())
486 {
487 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Invalid reduction axis"));
488 }
489
490 return std::visit([&](const auto &inputVec) -> tl::expected<Tensor, NeuralError>
491 {
492 using T = typename std::decay_t<decltype(inputVec)>::value_type;
493 const int64_t D = m_shape.size();
494
495 Shape outShape = m_shape;
496 outShape[axis] = 1;
497
498 std::vector<T> output(shapeNumElements(outShape));
499
500 int64_t innerStride = shapeToStride(m_shape, axis);
501 int64_t dimSize = m_shape[axis];
502 int64_t outerStride = innerStride * dimSize;
503
504 for (int64_t offset = 0; offset < inputVec.size(); offset += outerStride) {
505 for (int64_t i = 0; i < innerStride; ++i) {
506 // Compute flat index
507 int64_t outIdx = (offset / outerStride) * innerStride + i;
508 // Initialize accumulator
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]); // Apply operation
513 }
514 output[outIdx] = acc;
515 }
516 }
517 return Tensor::fromData(std::move(output), outShape); }, m_data);
518 }
519
550 template <typename Op>
551 tl::expected<Tensor, NeuralError> reduceWithIndex(const Op &op, uint64_t axis) const
552 {
553 if (axis >= m_shape.size())
554 {
555 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Invalid reduction axis"));
556 }
557
558 return std::visit([&](const auto &inputVec) -> tl::expected<Tensor, NeuralError>
559 {
560 using T = typename std::decay_t<decltype(inputVec)>::value_type;
561
562 Shape outShape = m_shape;
563 outShape[axis] = 1;
564
565 std::vector<int64_t> output(shapeNumElements(outShape));
566
567 int64_t innerStride = shapeToStride(m_shape, axis);
568 int64_t dimSize = m_shape[axis];
569 int64_t outerStride = innerStride * dimSize;
570
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);
578 }
579 output[outIdx] = static_cast<int64_t>(acc.first);
580 }
581 }
582
583 return Tensor::fromData(std::move(output), outShape); }, m_data);
584 }
585
614 template <typename Func>
615 tl::expected<Tensor, NeuralError> map(Func &&f) const
616 {
617 return std::visit([&](const auto &inputVec) -> tl::expected<Tensor, NeuralError>
618 {
619 using T = typename std::decay_t<decltype(inputVec)>::value_type;
620 using V = std::decay_t<decltype(f(std::declval<T>()))>;
621
622 std::vector<V> output;
623 output.resize(inputVec.size());
624
625 std::transform(inputVec.begin(), inputVec.end(), output.begin(),
626 [&](const T &val) -> V
627 {
628 return f(val);
629 });
630
631 return Tensor::fromData(std::move(output), m_shape); },
632 m_data);
633 }
634
665 template <typename Func>
666 tl::expected<Tensor, NeuralError> map(Func &&func, int axis) const
667 {
668 // Sanity check axis
669 if (axis < 0 || axis > static_cast<int>(m_shape.size()))
670 {
671 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Invalid axis to insert new dimension."));
672 }
673
674 return std::visit([&](const auto &input) -> tl::expected<Tensor, NeuralError>
675 {
676 using T = typename std::decay_t<decltype(input)>::value_type;
677 using Traits = map_array_traits<Func, T>;
678 using U = typename Traits::value_type;
679 constexpr size_t N = Traits::size;
680
681 if (input.empty()) {
682 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Tensor data is empty."));
683 }
684
685 // Compute the original and new shapes
686 Shape newShape = m_shape;
687 newShape.insert(newShape.begin() + axis, N);
688
689 const int64_t oldSize = shapeNumElements(m_shape);
690 const int64_t newSize = oldSize * N;
691
692 std::vector<U> outData(newSize);
693
694 // Precompute strides
695 std::vector<int64_t> oldStrides = computeStrides(m_shape);
696 std::vector<int64_t> newStrides = computeStrides(newShape);
697
698
699 // Main loop: for each index in the old tensor, place mapped values in the new buffer
700 for (int64_t idx = 0; idx < oldSize; ++idx) {
701 // Convert flat idx to N-dimensional index
702 std::vector<int64_t> coord = unravelIndex(idx, oldStrides);
703
704 // Apply function to get array<U, N>
705 auto mapped = func(input[idx]);
706
707 // Insert mapped[N] into new buffer at axis
708 coord.insert(coord.begin() + axis, 0);
709 for (int64_t i = 0; i < static_cast<int64_t>(N); ++i) {
710 coord[axis] = i;
711 int64_t flatIdx = ravelIndex(coord, newStrides);
712 outData[flatIdx] = mapped[i];
713 }
714 }
715
716 return Tensor::fromData(std::move(outData), newShape); }, m_data);
717 }
718
724 tl::expected<void, NeuralError> squeeze();
725
731 tl::expected<void, NeuralError> squeeze(int64_t axis);
732
738 tl::expected<void, NeuralError> unsqueeze(int64_t axis);
739
740 private:
741 TensorData m_data;
742 Shape m_shape;
743
744 static inline std::vector<int64_t> computeStrides(const Shape &shape)
745 {
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];
749 return strides;
750 }
751
752 static inline std::vector<int64_t> unravelIndex(int64_t index, const std::vector<int64_t> &strides)
753 {
754 std::vector<int64_t> coords(strides.size());
755 for (size_t i = 0; i < strides.size(); ++i)
756 {
757 coords[i] = index / strides[i];
758 index %= strides[i];
759 }
760 return coords;
761 }
762
763 static inline int64_t ravelIndex(const std::vector<int64_t> &coords, const std::vector<int64_t> &strides)
764 {
765 int64_t index = 0;
766 for (size_t i = 0; i < coords.size(); ++i)
767 index += coords[i] * strides[i];
768 return index;
769 }
770
779 template <typename T, int CV_TYPE>
780 static tl::expected<Tensor, NeuralError> fromCvMatsTyped(const std::vector<cv::Mat> &mats)
781 {
782 if (mats.empty())
783 {
784 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Input vector of cv::Mat is empty"));
785 }
786
787 int channels = mats[0].channels();
788 int height = mats[0].rows;
789 int width = mats[0].cols;
790
791 Shape shape = {static_cast<int64_t>(mats.size()), static_cast<int64_t>(channels), static_cast<int64_t>(height), static_cast<int64_t>(width)};
792 auto totalSize = shapeNumElements(shape);
793
794 std::vector<T> data;
795 data.reserve(totalSize);
796
797 for (const auto &mat : mats)
798 {
799 if (mat.depth() != CV_TYPE)
800 {
801 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "All cv::Mat must have the same data type"));
802 }
803
804 if (mat.channels() != channels || mat.rows != height || mat.cols != width)
805 {
806 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "All cv::Mat must have the same size and number of channels"));
807 }
808
809 std::vector<cv::Mat> splitted;
810 cv::split(mat, splitted);
811
812 for (int c = 0; c < channels; ++c)
813 {
814 // data.insert(data.end(), splitted[c].datastart, splitted[c].dataend);
815 const T *channelData = splitted[c].ptr<T>();
816 data.insert(data.end(), channelData, channelData + height * width);
817 }
818 }
819
820 return fromData(std::move(data), shape);
821 }
822
826 static cv::Mat preprocessCvMat(const cv::Mat &mat, const Shape &shape, float scale)
827 {
828 cv::Mat tmp;
829 const cv::Mat *matPtr = &mat; // points to the mat were currently working with
830
831 // if 3d, swap red and blue channels from BGR (opencv-standard) to RGB (anything else)
832 if (mat.channels() == 3)
833 {
834 cv::cvtColor(*matPtr, tmp, cv::COLOR_BGR2RGB);
835 matPtr = &tmp;
836 }
837
838 // resize if necessary
839 cv::Size size(shape[shape.size() - 2], shape[shape.size() - 1]);
840 if (size != cv::Size(-1,-1) && size != matPtr->size())
841 {
842 cv::resize(*matPtr, tmp, size, 0, 0, cv::INTER_AREA);
843 matPtr = &tmp;
844 }
845
846 // convert to float and apply scale
847 matPtr->convertTo(tmp, CV_32F, scale);
848 return tmp;
849 }
850
854 static cv::Mat preprocessCvMat(const cv::Mat &mat, const Shape &shape, float scale, const std::vector<float> &mean, const std::vector<float> &std)
855 {
856 cv::Mat tmp = preprocessCvMat(mat, shape, scale); // first do the basic preprocessing
857
858 // check if mean and std are provided and have the correct size
859 assert(mean.size() == tmp.channels());
860 assert(std.size() == tmp.channels());
861 // check that std is not zero
862 assert(std::all_of(std.begin(), std.end(), [](float s) { return s != 0.0f; }));
863
864 // subtract mean from each channel
865 int channels = tmp.channels();
866 int rows = tmp.rows;
867 int cols = tmp.cols * channels;
868
869 if (tmp.isContinuous())
870 {
871 cols *= rows;
872 rows = 1;
873 }
874
875 for (int i = 0; i < rows; ++i)
876 {
877 float *ptr = tmp.ptr<float>(i);
878 for (int j = 0; j < cols; j += channels)
879 {
880 for (int c = 0; c < channels; ++c)
881 {
882 ptr[j + c] = (ptr[j + c] - mean[c]) / std[c]; // apply mean and std
883 }
884 }
885 }
886
887 return tmp;
888 }
889
890 friend class OrtNeuralNet;
891 };
892}
893
894#ifndef TENSOR_DEBUG_PRINT
895#ifdef NDEBUG
896#define TENSOR_DEBUG_PRINT(tensor) ((void)0);
897#else
898#include <iostream>
899#define TENSOR_DEBUG_PRINT(tensor) (std::cout << (tensor).toString() << std::endl);
900#endif
901#endif
902
903#ifndef SHAPE_DEBUG_PRINT
904#ifdef NDEBUG
905#define SHAPE_DEBUG_PRINT(shape) ((void)0);
906#else
907#include <iostream>
908#define SHAPE_DEBUG_PRINT(shape) (std::cout << NN::shapeToString(shape) << std::endl);
909#endif
910#endif
911
912#ifndef RETURN_ON_ERROR
913#define RETURN_ON_ERROR(expr, retVal) \
914 do \
915 { \
916 auto _res = (expr); \
917 if (!_res) \
918 { \
919 std::cerr << "Error: " << _res.error() << std::endl; \
920 return (retVal); \
921 } \
922 } while (0);
923#endif
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