iVS3D v2.0.9
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
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);
248
269 static tl::expected<Tensor, NeuralError> fromCvMats(const std::vector<cv::Mat> &mats);
270
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);
297
312 template <typename T>
313 static tl::expected<Tensor, NeuralError> fromData(const std::vector<T> &data, const Shape &shape)
314 {
315 return fromData(std::vector<T>(data), shape); // delegates to move overload
316 }
317
342 template <typename T>
343 static tl::expected<Tensor, NeuralError> fromData(std::vector<T> &&data, const Shape &shape)
344 {
345 if (static_cast<size_t>(shapeNumElements(shape)) != data.size())
346 {
347 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Data size does not match shape"));
348 }
349
350 Tensor t;
351 t.m_shape = shape;
352 t.m_data = std::move(data);
353 return t;
354 }
355
385 template <typename T>
386 tl::expected<std::vector<T>, NeuralError> toVector() const
387 {
388 if (std::holds_alternative<std::vector<T>>(m_data))
389 {
390 return std::get<std::vector<T>>(m_data);
391 }
392 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Tensor does not hold requested data type"));
393 }
394
401 tl::expected<cv::Mat, NeuralError> toCvMat() const;
402
408 const Shape &shape() const { return m_shape; }
409
415 std::string toString() const;
416
423 tl::expected<void, NeuralError> reshape(const Shape &newShape);
424
430 int64_t numElements() const;
431
438 bool empty() const
439 {
440 return m_shape.empty() || shapeNumElements(m_shape) == 0;
441 }
442
450 {
451 return std::visit([](const auto &vec) -> TensorType
452 {
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;
460 } else {
461 return TensorType::Invalid; // Unsupported type
462 } }, m_data);
463 };
464
485 template <typename Op>
486 tl::expected<Tensor, NeuralError> reduce(const Op &op, uint64_t axis) const
487 {
488 if (axis >= m_shape.size())
489 {
490 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Invalid reduction axis"));
491 }
492
493 return std::visit([&](const auto &inputVec) -> tl::expected<Tensor, NeuralError>
494 {
495 using T = typename std::decay_t<decltype(inputVec)>::value_type;
496 const int64_t D = m_shape.size();
497
498 Shape outShape = m_shape;
499 outShape[axis] = 1;
500
501 std::vector<T> output(shapeNumElements(outShape));
502
503 int64_t innerStride = shapeToStride(m_shape, axis);
504 int64_t dimSize = m_shape[axis];
505 int64_t outerStride = innerStride * dimSize;
506
507 for (int64_t offset = 0; offset < inputVec.size(); offset += outerStride) {
508 for (int64_t i = 0; i < innerStride; ++i) {
509 // Compute flat index
510 int64_t outIdx = (offset / outerStride) * innerStride + i;
511 // Initialize accumulator
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]); // Apply operation
516 }
517 output[outIdx] = acc;
518 }
519 }
520 return Tensor::fromData(std::move(output), outShape); }, m_data);
521 }
522
553 template <typename Op>
554 tl::expected<Tensor, NeuralError> reduceWithIndex(const Op &op, uint64_t axis) const
555 {
556 if (axis >= m_shape.size())
557 {
558 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Invalid reduction axis"));
559 }
560
561 return std::visit([&](const auto &inputVec) -> tl::expected<Tensor, NeuralError>
562 {
563 using T = typename std::decay_t<decltype(inputVec)>::value_type;
564
565 Shape outShape = m_shape;
566 outShape[axis] = 1;
567
568 std::vector<int64_t> output(shapeNumElements(outShape));
569
570 int64_t innerStride = shapeToStride(m_shape, axis);
571 int64_t dimSize = m_shape[axis];
572 int64_t outerStride = innerStride * dimSize;
573
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);
581 }
582 output[outIdx] = static_cast<int64_t>(acc.first);
583 }
584 }
585
586 return Tensor::fromData(std::move(output), outShape); }, m_data);
587 }
588
617 template <typename Func>
618 tl::expected<Tensor, NeuralError> map(Func &&f) const
619 {
620 return std::visit([&](const auto &inputVec) -> tl::expected<Tensor, NeuralError>
621 {
622 using T = typename std::decay_t<decltype(inputVec)>::value_type;
623 using V = std::decay_t<decltype(f(std::declval<T>()))>;
624
625 std::vector<V> output;
626 output.resize(inputVec.size());
627
628 std::transform(inputVec.begin(), inputVec.end(), output.begin(),
629 [&](const T &val) -> V
630 {
631 return f(val);
632 });
633
634 return Tensor::fromData(std::move(output), m_shape); },
635 m_data);
636 }
637
668 template <typename Func>
669 tl::expected<Tensor, NeuralError> map(Func &&func, int axis) const
670 {
671 // Sanity check axis
672 if (axis < 0 || axis > static_cast<int>(m_shape.size()))
673 {
674 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Invalid axis to insert new dimension."));
675 }
676
677 return std::visit([&](const auto &input) -> tl::expected<Tensor, NeuralError>
678 {
679 using T = typename std::decay_t<decltype(input)>::value_type;
680 using Traits = map_array_traits<Func, T>;
681 using U = typename Traits::value_type;
682 constexpr size_t N = Traits::size;
683
684 if (input.empty()) {
685 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Tensor data is empty."));
686 }
687
688 // Compute the original and new shapes
689 Shape newShape = m_shape;
690 newShape.insert(newShape.begin() + axis, N);
691
692 const int64_t oldSize = shapeNumElements(m_shape);
693 const int64_t newSize = oldSize * N;
694
695 std::vector<U> outData(newSize);
696
697 // Precompute strides
698 std::vector<int64_t> oldStrides = computeStrides(m_shape);
699 std::vector<int64_t> newStrides = computeStrides(newShape);
700
701
702 // Main loop: for each index in the old tensor, place mapped values in the new buffer
703 for (int64_t idx = 0; idx < oldSize; ++idx) {
704 // Convert flat idx to N-dimensional index
705 std::vector<int64_t> coord = unravelIndex(idx, oldStrides);
706
707 // Apply function to get array<U, N>
708 auto mapped = func(input[idx]);
709
710 // Insert mapped[N] into new buffer at axis
711 coord.insert(coord.begin() + axis, 0);
712 for (int64_t i = 0; i < static_cast<int64_t>(N); ++i) {
713 coord[axis] = i;
714 int64_t flatIdx = ravelIndex(coord, newStrides);
715 outData[flatIdx] = mapped[i];
716 }
717 }
718
719 return Tensor::fromData(std::move(outData), newShape); }, m_data);
720 }
721
727 tl::expected<void, NeuralError> squeeze();
728
734 tl::expected<void, NeuralError> squeeze(int64_t axis);
735
741 tl::expected<void, NeuralError> unsqueeze(int64_t axis);
742
743 private:
744 TensorData m_data;
745 Shape m_shape;
746
747 static inline std::vector<int64_t> computeStrides(const Shape &shape)
748 {
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];
752 return strides;
753 }
754
755 static inline std::vector<int64_t> unravelIndex(int64_t index, const std::vector<int64_t> &strides)
756 {
757 std::vector<int64_t> coords(strides.size());
758 for (size_t i = 0; i < strides.size(); ++i)
759 {
760 coords[i] = index / strides[i];
761 index %= strides[i];
762 }
763 return coords;
764 }
765
766 static inline int64_t ravelIndex(const std::vector<int64_t> &coords, const std::vector<int64_t> &strides)
767 {
768 int64_t index = 0;
769 for (size_t i = 0; i < coords.size(); ++i)
770 index += coords[i] * strides[i];
771 return index;
772 }
773
782 template <typename T, int CV_TYPE>
783 static tl::expected<Tensor, NeuralError> fromCvMatsTyped(const std::vector<cv::Mat> &mats)
784 {
785 if (mats.empty())
786 {
787 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "Input vector of cv::Mat is empty"));
788 }
789
790 int channels = mats[0].channels();
791 int height = mats[0].rows;
792 int width = mats[0].cols;
793
794 Shape shape = {static_cast<int64_t>(mats.size()), static_cast<int64_t>(channels), static_cast<int64_t>(height), static_cast<int64_t>(width)};
795 auto totalSize = shapeNumElements(shape);
796
797 std::vector<T> data;
798 data.reserve(totalSize);
799
800 for (const auto &mat : mats)
801 {
802 if (mat.depth() != CV_TYPE)
803 {
804 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "All cv::Mat must have the same data type"));
805 }
806
807 if (mat.channels() != channels || mat.rows != height || mat.cols != width)
808 {
809 return tl::unexpected(NeuralError(ErrorCode::InvalidArgument, "All cv::Mat must have the same size and number of channels"));
810 }
811
812 std::vector<cv::Mat> splitted;
813 cv::split(mat, splitted);
814
815 for (int c = 0; c < channels; ++c)
816 {
817 // data.insert(data.end(), splitted[c].datastart, splitted[c].dataend);
818 const T *channelData = splitted[c].ptr<T>();
819 data.insert(data.end(), channelData, channelData + height * width);
820 }
821 }
822
823 return fromData(std::move(data), shape);
824 }
825
829 static cv::Mat preprocessCvMat(const cv::Mat &mat, const Shape &shape, float scale, int gridSize)
830 {
831 cv::Mat tmp;
832 const cv::Mat *matPtr = &mat; // points to the mat were currently working with
833
834 // if 3d, swap red and blue channels from BGR (opencv-standard) to RGB (anything else)
835 if (mat.channels() == 3)
836 {
837 cv::cvtColor(*matPtr, tmp, cv::COLOR_BGR2RGB);
838 matPtr = &tmp;
839 }
840
841 // resize if necessary
842 cv::Size size(shape[shape.size() - 2], shape[shape.size() - 1]);
843
844 // a specific size is requested in the shape and the image does not match this yet
845 if (size == cv::Size(-1,-1)) {
846 // size is dynamic, so we do not resize here, unless a gridSize > 1 is requested
847 if (gridSize > 1) {
848 cv::Size newSize(
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);
852 matPtr = &tmp;
853 }
854 }
855 else {
856 // size is static, so we resize to the requested size
857 if (size != matPtr->size())
858 {
859 cv::resize(*matPtr, tmp, size, 0, 0, cv::INTER_AREA);
860 matPtr = &tmp;
861 }
862 }
863
864
865 // convert to float and apply scale
866 matPtr->convertTo(tmp, CV_32F, scale);
867 return tmp;
868 }
869
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)
874 {
875 cv::Mat tmp = preprocessCvMat(mat, shape, scale, gridSize); // first do the basic preprocessing
876
877 // check if mean and std are provided and have the correct size
878 assert(mean.size() == tmp.channels());
879 assert(std.size() == tmp.channels());
880 // check that std is not zero
881 assert(std::all_of(std.begin(), std.end(), [](float s) { return s != 0.0f; }));
882
883 // subtract mean from each channel
884 int channels = tmp.channels();
885 int rows = tmp.rows;
886 int cols = tmp.cols * channels;
887
888 if (tmp.isContinuous())
889 {
890 cols *= rows;
891 rows = 1;
892 }
893
894 for (int i = 0; i < rows; ++i)
895 {
896 float *ptr = tmp.ptr<float>(i);
897 for (int j = 0; j < cols; j += channels)
898 {
899 for (int c = 0; c < channels; ++c)
900 {
901 ptr[j + c] = (ptr[j + c] - mean[c]) / std[c]; // apply mean and std
902 }
903 }
904 }
905
906 return tmp;
907 }
908
909 friend class OrtNeuralNet;
910 };
911}
912
913#ifndef TENSOR_DEBUG_PRINT
914#ifdef NDEBUG
915#define TENSOR_DEBUG_PRINT(tensor) ((void)0);
916#else
917#include <iostream>
918#define TENSOR_DEBUG_PRINT(tensor) (std::cout << (tensor).toString() << std::endl);
919#endif
920#endif
921
922#ifndef SHAPE_DEBUG_PRINT
923#ifdef NDEBUG
924#define SHAPE_DEBUG_PRINT(shape) ((void)0);
925#else
926#include <iostream>
927#define SHAPE_DEBUG_PRINT(shape) (std::cout << NN::shapeToString(shape) << std::endl);
928#endif
929#endif
930
931#ifndef RETURN_ON_ERROR
932#define RETURN_ON_ERROR(expr, retVal) \
933 do \
934 { \
935 auto _res = (expr); \
936 if (!_res) \
937 { \
938 std::cerr << "Error: " << _res.error() << std::endl; \
939 return (retVal); \
940 } \
941 } while (0);
942#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: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