diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index 174b4d02f2..4865ea7538 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #ifdef HAVE_OPENCL @@ -106,13 +107,102 @@ int ActivationLayer::getLayouts(const std::vector& actualInputs, } struct PowerFunctor; +struct AbsValFunctor; +struct SignFunctor; template struct ElementWiseIntDispatch { + static inline bool supports(const Func&, int) { return false; } static inline bool apply(const Func&, const Mat&, Mat&) { return false; } }; +static inline bool isIntegerDepth(int depth) +{ + return depth == CV_8U || depth == CV_8S || depth == CV_16U || depth == CV_16S || + depth == CV_32U || depth == CV_32S || depth == CV_64U || depth == CV_64S; +} + +static inline bool isUnsignedDepth(int depth) +{ + return depth == CV_8U || depth == CV_16U || depth == CV_32U || depth == CV_64U; +} + +template static inline T intAbs(T x, std::true_type) +{ + // Negating the type's minimum is undefined; the unsigned round trip wraps instead. + typedef typename std::make_unsigned::type UT; + return x < 0 ? (T)(UT(0) - (UT)x) : x; +} + +template static inline T intAbs(T x, std::false_type) { return x; } + +template static inline T intSign(T x, std::true_type) { return (T)((x > 0) - (x < 0)); } + +template static inline T intSign(T x, std::false_type) { return (T)(x != 0); } + +struct IntAbsOp +{ + template static inline T apply(T x) + { + return intAbs(x, std::integral_constant::is_signed>()); + } +}; + +struct IntSignOp +{ + template static inline T apply(T x) + { + return intSign(x, std::integral_constant::is_signed>()); + } +}; + +template static inline void blockedParallelFor(size_t total, Body&& body) +{ + const size_t BLOCK_SIZE = 1 << 16; + parallel_for_(Range(0, (int)((total + BLOCK_SIZE - 1) / BLOCK_SIZE)), + [&](const Range& r) + { + for (int b = r.start; b < r.end; b++) + { + size_t start = (size_t)b * BLOCK_SIZE; + body(start, std::min(BLOCK_SIZE, total - start)); + } + }); +} + +template static inline void intUnaryKernel(const Mat& src, Mat& dst) +{ + const T* srcptr = src.ptr(); + T* dstptr = dst.ptr(); + + blockedParallelFor(src.total(), [&](size_t start, size_t len) + { + for (size_t i = start; i < start + len; i++) + dstptr[i] = Op::apply(srcptr[i]); + }); +} + +template static inline bool intUnaryDispatch(const Mat& src, Mat& dst) +{ + if (src.type() != dst.type()) + return false; + + switch (src.depth()) + { + case CV_8U: intUnaryKernel(src, dst); break; + case CV_8S: intUnaryKernel(src, dst); break; + case CV_16U: intUnaryKernel(src, dst); break; + case CV_16S: intUnaryKernel(src, dst); break; + case CV_32U: intUnaryKernel(src, dst); break; + case CV_32S: intUnaryKernel(src, dst); break; + case CV_64U: intUnaryKernel(src, dst); break; + case CV_64S: intUnaryKernel(src, dst); break; + default: return false; + } + return true; +} + template class ElementWiseLayer : public Func::Layer { @@ -223,11 +313,33 @@ public: return true; } + void getTypes(const std::vector& inputs, + const int requiredOutputs, + const int requiredInternals, + std::vector& outputs, + std::vector& internals) const CV_OVERRIDE + { + CV_Assert(inputs.size()); + for (auto input : inputs) + { + // Types the functor has no integer kernel for follow the default gate. + if (!ElementWiseIntDispatch::supports(func, input)) + { + LayerInfo::getTypes(inputs, requiredOutputs, requiredInternals, outputs, internals); + return; + } + } + + outputs.assign(requiredOutputs, inputs[0]); + internals.assign(requiredInternals, inputs[0]); + } + void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE { CV_TRACE_FUNCTION(); - CV_OCL_RUN(IS_DNN_OPENCL_TARGET(this->preferableTarget), + // The OCL kernels compute in float, which would silently round wide integers. + CV_OCL_RUN(IS_DNN_OPENCL_TARGET(this->preferableTarget) && !isIntegerDepth(inputs_arr.depth()), func.applyOCL(inputs_arr, outputs_arr, internals_arr)) if (inputs_arr.depth() == CV_16F) @@ -264,15 +376,9 @@ public: const float* srcptr = src.ptr(); float* dstptr = dst.ptr(); - const size_t BLOCK_SIZE = 1 << 16; - parallel_for_(Range(0, (int)((total + BLOCK_SIZE - 1) / BLOCK_SIZE)), - [&](const Range& r) { - for (int b = r.start; b < r.end; b++) { - size_t start = b * BLOCK_SIZE; - size_t len = std::min(BLOCK_SIZE, total - start); - activFunc(srcptr + start, dstptr + start, len, params); - } - }); + blockedParallelFor(total, [&](size_t start, size_t len) { + activFunc(srcptr + start, dstptr + start, len, params); + }); continue; } @@ -1540,6 +1646,26 @@ struct AbsValFunctor : public BaseDefaultFunctor template<> const char* const AbsValFunctor::BaseDefaultFunctor::ocl_kernel_name = "AbsValForward"; +template<> +struct ElementWiseIntDispatch +{ + static inline bool supports(const AbsValFunctor&, int depth) { return isIntegerDepth(depth); } + + static inline bool apply(const AbsValFunctor&, const Mat& src, Mat& dst) + { + if (src.type() != dst.type()) + return false; + + // |x| leaves an unsigned value unchanged. + if (isUnsignedDepth(src.depth())) + { + src.copyTo(dst); + return true; + } + return intUnaryDispatch(src, dst); + } +}; + struct BNLLFunctor : public BaseDefaultFunctor { typedef BNLLLayer Layer; @@ -2967,14 +3093,10 @@ struct PowerFunctor : public BaseFunctor template<> struct ElementWiseIntDispatch { - static inline bool apply(const PowerFunctor& func, const Mat& src, Mat& dst) + // Only the degenerate form is representable in integers, so support depends on the + // functor's parameters and not on the depth alone. + static inline bool integerScale(const PowerFunctor& func, int64_t& scale) { - if (src.type() != dst.type()) - return false; - const int depth = src.depth(); - if (depth != CV_32S && depth != CV_64S) - return false; - if (func.power != 1.f) return false; if (func.shift != 0.f) @@ -2984,7 +3106,27 @@ struct ElementWiseIntDispatch const double scale_d = (double)func.scale; if (std::floor(scale_d) != scale_d) return false; - const int64_t scale = (int64_t)scale_d; + scale = (int64_t)scale_d; + return true; + } + + static inline bool supports(const PowerFunctor& func, int depth) + { + int64_t scale; + return (depth == CV_32S || depth == CV_64S) && integerScale(func, scale); + } + + static inline bool apply(const PowerFunctor& func, const Mat& src, Mat& dst) + { + if (src.type() != dst.type()) + return false; + const int depth = src.depth(); + if (depth != CV_32S && depth != CV_64S) + return false; + + int64_t scale; + if (!integerScale(func, scale)) + return false; const size_t n = src.total(); if (depth == CV_32S) @@ -2999,8 +3141,9 @@ struct ElementWiseIntDispatch { const int64_t* sp = src.ptr(); int64_t* dp = dst.ptr(); + // Unsigned so the wrap at the type minimum is defined rather than overflow. for (size_t i = 0; i < n; ++i) - dp[i] = sp[i] * scale; + dp[i] = (int64_t)((uint64_t)sp[i] * (uint64_t)scale); return true; } } @@ -3345,6 +3488,17 @@ struct SignFunctor : public BaseDefaultFunctor template<> const char* const SignFunctor::BaseDefaultFunctor::ocl_kernel_name = "SignForward"; +template<> +struct ElementWiseIntDispatch +{ + static inline bool supports(const SignFunctor&, int depth) { return isIntegerDepth(depth); } + + static inline bool apply(const SignFunctor&, const Mat& src, Mat& dst) + { + return intUnaryDispatch(src, dst); + } +}; + struct ShrinkFunctor : public BaseDefaultFunctor { diff --git a/modules/dnn/src/layers/gatherND.cpp b/modules/dnn/src/layers/gatherND.cpp index 51b71937f9..dfdd66c0dd 100644 --- a/modules/dnn/src/layers/gatherND.cpp +++ b/modules/dnn/src/layers/gatherND.cpp @@ -27,18 +27,11 @@ public: CV_CheckType(indicesType, indicesType == CV_32S || indicesType == CV_64S, "GatherND: indices must be CV_32S or CV_64S"); - if (preferableTarget == DNN_TARGET_OPENCL_FP16) - { - CV_CheckType(dataType, dataType == CV_16F || dataType == CV_8S || dataType == CV_8U || - dataType == CV_32S || dataType == CV_64S || dataType == CV_Bool, - "GatherND: unsupported data type for OpenCL FP16 target"); - } - else - { - CV_CheckType(dataType, dataType == CV_32F || dataType == CV_8S || dataType == CV_8U || - dataType == CV_32S || dataType == CV_64S || dataType == CV_Bool, - "GatherND: unsupported data type"); - } + CV_CheckType(dataType, dataType == CV_16F || dataType == CV_32F || dataType == CV_64F || + dataType == CV_8S || dataType == CV_8U || dataType == CV_16U || + dataType == CV_16S || dataType == CV_32U || dataType == CV_32S || + dataType == CV_64U || dataType == CV_64S || dataType == CV_Bool, + "GatherND: unsupported data type"); outputs.resize(1, dataType); internals.clear(); @@ -87,42 +80,29 @@ public: const Mat& indices = inputs[1]; Mat& out = outputs[0]; - int dtype = data.depth(); int itype = indices.depth(); switch (itype) { - case CV_32S: - { - switch (dtype) { - case CV_8U: - case CV_Bool: forward_impl(data, indices, out); break; - case CV_8S: forward_impl(data, indices, out); break; - case CV_32S: forward_impl(data, indices, out); break; - case CV_16F: forward_impl(data, indices, out); break; - case CV_32F: forward_impl(data, indices, out); break; - case CV_64F: forward_impl(data, indices, out); break; - default: CV_Error(Error::StsNotImplemented, "Unsupported data type"); - } - } break; - case CV_64S: - { - switch (dtype) { - case CV_8U: - case CV_Bool: forward_impl(data, indices, out); break; - case CV_8S: forward_impl(data, indices, out); break; - case CV_32S: forward_impl(data, indices, out); break; - case CV_16F: forward_impl(data, indices, out); break; - case CV_32F: forward_impl(data, indices, out); break; - case CV_64F: forward_impl(data, indices, out); break; - case CV_64S: forward_impl(data, indices, out); break; - default: CV_Error(Error::StsNotImplemented, "Unsupported data type"); - } - } break; + case CV_32S: widthDispatch(data, indices, out); break; + case CV_64S: widthDispatch(data, indices, out); break; default: CV_Error(Error::StsNotImplemented, "Unsupported indices type"); } } + // Gathering copies elements without interpreting them, so only the width matters. + template + void widthDispatch(const Mat& data, const Mat& indices, Mat& out) + { + switch (data.elemSize()) { + case 1: forward_impl(data, indices, out); break; + case 2: forward_impl(data, indices, out); break; + case 4: forward_impl(data, indices, out); break; + case 8: forward_impl(data, indices, out); break; + default: CV_Error(Error::StsNotImplemented, "Unsupported data type"); + } + } + template void forward_impl(const Mat& data, const Mat& indices, Mat& out) { @@ -174,10 +154,7 @@ public: offset += (size_t)batch_idx * data_strides[batch_dims - 1]; } // copy data from data to out - for (size_t j = 0; j < inner_size; ++j) - { - out_ptr[i * inner_size + j] = data_ptr[offset + j]; - } + std::memcpy(out_ptr + i * inner_size, data_ptr + offset, inner_size * sizeof(dT)); } }, nstripes); } diff --git a/modules/dnn/src/layers/gather_elements_layer.cpp b/modules/dnn/src/layers/gather_elements_layer.cpp index f71923ebbd..9e40c31233 100644 --- a/modules/dnn/src/layers/gather_elements_layer.cpp +++ b/modules/dnn/src/layers/gather_elements_layer.cpp @@ -67,7 +67,8 @@ public: std::vector& internals) const CV_OVERRIDE { CV_CheckEQ(inputs.size(), (size_t)2, ""); - CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_32S || inputs[0] == CV_64S || inputs[0] == CV_16F || inputs[0] == CV_8U || inputs[0] == CV_8S || inputs[0] == CV_Bool, ""); + CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_32S || inputs[0] == CV_64S || inputs[0] == CV_16F || inputs[0] == CV_8U || inputs[0] == CV_8S || inputs[0] == CV_Bool || + inputs[0] == CV_64F || inputs[0] == CV_16U || inputs[0] == CV_16S || inputs[0] == CV_32U || inputs[0] == CV_64U, ""); CV_CheckType(inputs[1], inputs[1] == CV_64S || inputs[1] == CV_32S, ""); outputs.assign(1, inputs[0]); } @@ -154,31 +155,23 @@ public: }; } + // Gathering copies elements without interpreting them, so only the width matters. template inline void typeDispatch(const int type, Args&&... args) { - switch (type) + switch (CV_ELEM_SIZE(type)) { - case CV_Bool: - forward_impl(std::forward(args)...); - break; - case CV_8U: + case 1: forward_impl(std::forward(args)...); break; - case CV_8S: - forward_impl(std::forward(args)...); + case 2: + forward_impl(std::forward(args)...); break; - case CV_16F: - forward_impl(std::forward(args)...); + case 4: + forward_impl(std::forward(args)...); break; - case CV_32S: - forward_impl(std::forward(args)...); - break; - case CV_64S: - forward_impl(std::forward(args)...); - break; - case CV_32F: - forward_impl(std::forward(args)...); + case 8: + forward_impl(std::forward(args)...); break; default: CV_Error(cv::Error::BadDepth, "DNN/GatherElements: Unsupported type."); diff --git a/modules/dnn/src/layers/scatterND_layer.cpp b/modules/dnn/src/layers/scatterND_layer.cpp index b0062993a8..4f0177b23a 100644 --- a/modules/dnn/src/layers/scatterND_layer.cpp +++ b/modules/dnn/src/layers/scatterND_layer.cpp @@ -79,7 +79,8 @@ public: std::vector& internals) const CV_OVERRIDE { CV_CheckEQ(inputs.size(), (size_t)3, ""); - CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_32S || inputs[0] == CV_64S || inputs[0] == CV_16F || inputs[0] == CV_8U || inputs[0] == CV_8S || inputs[0] == CV_Bool, ""); + CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_32S || inputs[0] == CV_64S || inputs[0] == CV_16F || inputs[0] == CV_8U || inputs[0] == CV_8S || inputs[0] == CV_Bool || + inputs[0] == CV_64F || inputs[0] == CV_16U || inputs[0] == CV_16S || inputs[0] == CV_32U || inputs[0] == CV_64U, ""); CV_CheckType(inputs[1], inputs[1] == CV_64S || inputs[1] == CV_32S, ""); CV_CheckTypeEQ(inputs[2], inputs[0], ""); outputs.assign(1, inputs[0]); @@ -196,15 +197,30 @@ public: case CV_8S: reductionDispatch(std::forward(args)...); break; + case CV_16U: + reductionDispatch(std::forward(args)...); + break; + case CV_16S: + reductionDispatch(std::forward(args)...); + break; + case CV_32U: + reductionDispatch(std::forward(args)...); + break; case CV_32S: reductionDispatch(std::forward(args)...); break; + case CV_64U: + reductionDispatch(std::forward(args)...); + break; case CV_64S: reductionDispatch(std::forward(args)...); break; case CV_32F: reductionDispatch(std::forward(args)...); break; + case CV_64F: + reductionDispatch(std::forward(args)...); + break; default: CV_Error(cv::Error::BadDepth, "Unsupported type."); }; diff --git a/modules/dnn/src/layers/scatter_layer.cpp b/modules/dnn/src/layers/scatter_layer.cpp index 12580bad14..84d9d235fb 100644 --- a/modules/dnn/src/layers/scatter_layer.cpp +++ b/modules/dnn/src/layers/scatter_layer.cpp @@ -73,7 +73,8 @@ public: std::vector& internals) const CV_OVERRIDE { CV_CheckEQ(inputs.size(), (size_t)3, ""); - CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_32S || inputs[0] == CV_64S || inputs[0] == CV_16F || inputs[0] == CV_8U || inputs[0] == CV_8S || inputs[0] == CV_Bool, ""); + CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_32S || inputs[0] == CV_64S || inputs[0] == CV_16F || inputs[0] == CV_8U || inputs[0] == CV_8S || inputs[0] == CV_Bool || + inputs[0] == CV_64F || inputs[0] == CV_16U || inputs[0] == CV_16S || inputs[0] == CV_32U || inputs[0] == CV_64U, ""); CV_CheckType(inputs[1], inputs[1] == CV_64S || inputs[1] == CV_32S, ""); CV_CheckTypeEQ(inputs[2], inputs[0], ""); outputs.assign(1, inputs[0]); @@ -191,15 +192,30 @@ public: case CV_8S: reductionDispatch(std::forward(args)...); break; + case CV_16U: + reductionDispatch(std::forward(args)...); + break; + case CV_16S: + reductionDispatch(std::forward(args)...); + break; + case CV_32U: + reductionDispatch(std::forward(args)...); + break; case CV_32S: reductionDispatch(std::forward(args)...); break; + case CV_64U: + reductionDispatch(std::forward(args)...); + break; case CV_64S: reductionDispatch(std::forward(args)...); break; case CV_32F: reductionDispatch(std::forward(args)...); break; + case CV_64F: + reductionDispatch(std::forward(args)...); + break; default: CV_Error(cv::Error::BadDepth, "Unsupported type."); }; diff --git a/modules/dnn/src/layers/slice2_layer.cpp b/modules/dnn/src/layers/slice2_layer.cpp index b8de726648..f310f564f0 100644 --- a/modules/dnn/src/layers/slice2_layer.cpp +++ b/modules/dnn/src/layers/slice2_layer.cpp @@ -405,6 +405,22 @@ private: parallel_for_(Range(0, parallel_size), body, nstripes); } + // Slicing copies elements without interpreting them, so only the width matters. + void run_parallel_by_width(const Mat& inp, Mat& out, + const std::vector& ranges, const std::vector& steps) + { + if (inp.elemSize() == 1) { + run_parallel(inp, out, ranges, steps); + } else if (inp.elemSize() == 2) { + run_parallel(inp, out, ranges, steps); + } else if (inp.elemSize() == 4) { + run_parallel(inp, out, ranges, steps); + } else { + CV_Assert(inp.elemSize() == 8); + run_parallel(inp, out, ranges, steps); + } + } + void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays) CV_OVERRIDE @@ -461,13 +477,7 @@ private: outs.resize(1); outs[0].fit(outShape, inpType); - if (inp.depth() == CV_32S) run_parallel(inp, outs[0], ranges, steps_vec); - else if (inp.depth() == CV_64S) run_parallel(inp, outs[0], ranges, steps_vec); - else if (inp.depth() == CV_16F) run_parallel(inp, outs[0], ranges, steps_vec); - else if (inp.depth() == CV_8S) run_parallel(inp, outs[0], ranges, steps_vec); - else if (inp.depth() == CV_8U) run_parallel(inp, outs[0], ranges, steps_vec); - else if (inp.depth() == CV_Bool) run_parallel(inp, outs[0], ranges, steps_vec); - else run_parallel(inp, outs[0], ranges, steps_vec); + run_parallel_by_width(inp, outs[0], ranges, steps_vec); } else { Mat inp = inputs_arr.getMat(0); std::vector& outs = outputs_arr.getUMatVecRef(); @@ -475,13 +485,7 @@ private: outs[0].fit(outShape, inpType); Mat temp(outShape, inpType); - if (inp.depth() == CV_32S) run_parallel(inp, temp, ranges, steps_vec); - else if (inp.depth() == CV_64S) run_parallel(inp, temp, ranges, steps_vec); - else if (inp.depth() == CV_16F) run_parallel(inp, temp, ranges, steps_vec); - else if (inp.depth() == CV_8S) run_parallel(inp, temp, ranges, steps_vec); - else if (inp.depth() == CV_8U) run_parallel(inp, temp, ranges, steps_vec); - else if (inp.depth() == CV_Bool) run_parallel(inp, temp, ranges, steps_vec); - else run_parallel(inp, temp, ranges, steps_vec); + run_parallel_by_width(inp, temp, ranges, steps_vec); temp.copyTo(outs[0]); } diff --git a/modules/dnn/test/test_int.cpp b/modules/dnn/test/test_int.cpp index f5a6ecb356..eb5f2bcb9e 100644 --- a/modules/dnn/test/test_int.cpp +++ b/modules/dnn/test/test_int.cpp @@ -18,8 +18,16 @@ int64_t getValueAt(const Mat &m, const int *indices) return m.at(indices); else if (m.type() == CV_8S) return m.at(indices); + else if (m.type() == CV_16U) + return m.at(indices); + else if (m.type() == CV_16S) + return m.at(indices); + else if (m.type() == CV_32U) + return m.at(indices); else if (m.type() == CV_32S) return m.at(indices); + else if (m.type() == CV_64U) + return (int64_t)m.at(indices); else if (m.type() == CV_64S) return m.at(indices); else @@ -35,8 +43,16 @@ int64_t getValueAt(const Mat &m, int index) return m.ptr()[index]; else if (m.type() == CV_8S) return m.ptr()[index]; + else if (m.type() == CV_16U) + return m.ptr()[index]; + else if (m.type() == CV_16S) + return m.ptr()[index]; + else if (m.type() == CV_32U) + return m.ptr()[index]; else if (m.type() == CV_32S) return m.ptr()[index]; + else if (m.type() == CV_64U) + return (int64_t)m.ptr()[index]; else if (m.type() == CV_64S) return m.ptr()[index]; else @@ -52,6 +68,15 @@ void fillRandom(Mat& m, int matType, Backend backend) cv::randu(m, 1000000000000000ll, 1000000000000100ll); else if (matType == CV_32S) cv::randu(m, 1000000000, 1000000100); + // CV_64U stays inside the int64_t range so getValueAt() round-trips exactly. + else if (matType == CV_64U) + cv::randu(m, 1000000000000000ll, 1000000000000100ll); + else if (matType == CV_32U) + cv::randu(m, 1000000000, 1000000100); + else if (matType == CV_16S) + cv::randu(m, -1000, 1000); + else if (matType == CV_16U) + cv::randu(m, 0, 1000); else if (matType == CV_8S) cv::randu(m, -50, 50); else if (matType == CV_8U) @@ -62,6 +87,55 @@ void fillRandom(Mat& m, int matType, Backend backend) CV_Error(Error::BadDepth, "Unsupported type"); } +void setValueAt(Mat &m, int index, int64_t value) +{ + if (m.type() == CV_Bool) + m.ptr()[index] = (bool)value; + else if (m.type() == CV_8U) + m.ptr()[index] = (uint8_t)value; + else if (m.type() == CV_8S) + m.ptr()[index] = (int8_t)value; + else if (m.type() == CV_16U) + m.ptr()[index] = (uint16_t)value; + else if (m.type() == CV_16S) + m.ptr()[index] = (int16_t)value; + else if (m.type() == CV_32U) + m.ptr()[index] = (uint32_t)value; + else if (m.type() == CV_32S) + m.ptr()[index] = (int32_t)value; + else if (m.type() == CV_64U) + m.ptr()[index] = (uint64_t)value; + else if (m.type() == CV_64S) + m.ptr()[index] = value; + else + CV_Error(Error::BadDepth, "Unsupported type"); +} + +int64_t minValueAt(int matType) +{ + if (matType == CV_8S) + return std::numeric_limits::min(); + else if (matType == CV_16S) + return std::numeric_limits::min(); + else if (matType == CV_32S) + return std::numeric_limits::min(); + else if (matType == CV_64S) + return std::numeric_limits::min(); + return 0; +} + +// Abs and Sign are decided by the sign of their input, and fillRandom keeps the 32-bit +// and 64-bit signed ranges above zero. +void fillRandomSigned(Mat& m, int matType, Backend backend) +{ + if (matType == CV_32S) + cv::randu(m, -1000000000, 1000000000); + else if (matType == CV_64S) + cv::randu(m, -1000000000000000ll, 1000000000000000ll); + else + fillRandom(m, matType, backend); +} + typedef testing::TestWithParam > > Test_NaryEltwise_Int; TEST_P(Test_NaryEltwise_Int, random) { @@ -219,6 +293,13 @@ TEST_P(Test_ScatterND_Int, random) Backend backend = get<0>(backend_target); Target target = get<1>(backend_target); + const bool wideIntType = matType == CV_16U || matType == CV_16S || + matType == CV_32U || matType == CV_64U; + if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && wideIntType) + applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH); // OpenVINO carries a tensor type for 8-bit, signed 32/64-bit and float depths only + if (backend == DNN_BACKEND_CUDA && wideIntType) + applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA); // The CUDA wrapper carries a tensor type for 8-bit, signed 32/64-bit and float depths only + std::vector inShape{2, 3, 4, 5}; Mat input(inShape, matType); fillRandom(input, matType, backend); @@ -245,18 +326,7 @@ TEST_P(Test_ScatterND_Int, random) } for (int i = 0; i < updatesValues.size(); ++i) - { - if (matType == CV_32S) - updates.ptr()[i] = updatesValues[i]; - else if (matType == CV_64S) - updates.ptr()[i] = updatesValues[i]; - else if (matType == CV_8S) - updates.ptr()[i] = updatesValues[i]; - else if (matType == CV_8U) - updates.ptr()[i] = updatesValues[i]; - else if (matType == CV_Bool) - updates.ptr()[i] = updatesValues[i]; - } + setValueAt(updates, i, updatesValues[i]); Net net; LayerParams lp; @@ -321,8 +391,94 @@ TEST_P(Test_ScatterND_Int, random) } } +typedef testing::TestWithParam > > Test_Scatter_Int; +TEST_P(Test_Scatter_Int, random) +{ + int matType = get<0>(GetParam()); + int indicesType = get<1>(GetParam()); + tuple backend_target= get<2>(GetParam()); + Backend backend = get<0>(backend_target); + Target target = get<1>(backend_target); + + const int axis = 2, scatterAt = 2; + std::vector inShape{2, 3, 4, 5}; + Mat input(inShape, matType); + fillRandom(input, matType, backend); + + // One update per (i0, i1, i3), all aimed at the same position along the axis. + std::vector idxShape{2, 3, 1, 5}; + Mat indices(idxShape, indicesType); + Mat updates(idxShape, matType); + for (int i = 0; i < (int)updates.total(); ++i) + { + if (indicesType == CV_32S) + indices.ptr()[i] = scatterAt; + else + indices.ptr()[i] = scatterAt; + setValueAt(updates, i, matType == CV_Bool ? 1 : i + 1); + } + + Net net; + LayerParams lp; + lp.type = "Scatter"; + lp.name = "testLayer"; + lp.set("axis", axis); + int id = net.addLayerToPrev(lp.name, lp.type, lp); + net.connect(0, 1, id, 1); + net.connect(0, 2, id, 2); + + std::vector inpNames(3); + inpNames[0] = "scatter_input"; + inpNames[1] = "scatter_indices"; + inpNames[2] = "scatter_updates"; + net.setInputsNames(inpNames); + net.setInput(input, inpNames[0]); + net.setInput(indices, inpNames[1]); + net.setInput(updates, inpNames[2]); + + net.setPreferableBackend(backend); + net.setPreferableTarget(target); + + Mat re; + re = net.forward(); + EXPECT_EQ(re.depth(), matType); + ASSERT_EQ(shape(input), shape(re)); + + std::vector reIndices(4); + for (int i0 = 0; i0 < input.size[0]; ++i0) + { + reIndices[0] = i0; + for (int i1 = 0; i1 < input.size[1]; ++i1) + { + reIndices[1] = i1; + for (int i2 = 0; i2 < input.size[2]; ++i2) + { + reIndices[2] = i2; + for (int i3 = 0; i3 < input.size[3]; ++i3) + { + reIndices[3] = i3; + if (i2 == scatterAt) + { + int flat = (i0 * idxShape[1] + i1) * idxShape[3] + i3; + EXPECT_EQ(getValueAt(re, reIndices.data()), getValueAt(updates, flat)); + } + else + EXPECT_EQ(getValueAt(re, reIndices.data()), getValueAt(input, reIndices.data())); + } + } + } + } +} + +// Only the OpenCV backend implements the integer kernels. +INSTANTIATE_TEST_CASE_P(/**/, Test_Scatter_Int, Combine( + testing::Values(CV_Bool, CV_8U, CV_8S, CV_16U, CV_16S, CV_32U, CV_32S, CV_64U, CV_64S), + testing::Values(CV_32S, CV_64S), + dnnBackendsAndTargets(false, false, true, false, false, false, false, false) +)); + INSTANTIATE_TEST_CASE_P(/**/, Test_ScatterND_Int, Combine( - testing::Values(CV_Bool, CV_8U, CV_8S, CV_32S, CV_64S), + testing::Values(CV_Bool, CV_8U, CV_8S, CV_16U, CV_16S, CV_32U, CV_32S, CV_64U, CV_64S), testing::Values(CV_32S, CV_64S), dnnBackendsAndTargets() )); @@ -676,6 +832,13 @@ TEST_P(Test_GatherElements_Int, random) Backend backend = get<0>(backend_target); Target target = get<1>(backend_target); + const bool wideIntType = matType == CV_16U || matType == CV_16S || + matType == CV_32U || matType == CV_64U; + if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && wideIntType) + applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH); // OpenVINO carries a tensor type for 8-bit, signed 32/64-bit and float depths only + if (backend == DNN_BACKEND_CUDA && wideIntType) + applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA); // The CUDA wrapper carries a tensor type for 8-bit, signed 32/64-bit and float depths only + std::vector inShape{2, 3, 4, 5}; Mat input(inShape, matType); fillRandom(input, matType, backend); @@ -734,7 +897,7 @@ TEST_P(Test_GatherElements_Int, random) } INSTANTIATE_TEST_CASE_P(/**/, Test_GatherElements_Int, Combine( - testing::Values(CV_Bool, CV_8U, CV_8S, CV_32S, CV_64S), + testing::Values(CV_Bool, CV_8U, CV_8S, CV_16U, CV_16S, CV_32U, CV_32S, CV_64U, CV_64S), testing::Values(CV_32S, CV_64S), dnnBackendsAndTargets() )); @@ -1255,4 +1418,216 @@ INSTANTIATE_TEST_CASE_P(/**/, Test_Reduce_Int, Combine( dnnBackendsAndTargets() )); +typedef testing::TestWithParam > > Test_Abs_Int; +TEST_P(Test_Abs_Int, random) +{ + int matType = get<0>(GetParam()); + tuple backend_target= get<1>(GetParam()); + Backend backend = get<0>(backend_target); + Target target = get<1>(backend_target); + + std::vector inShape{2, 3, 4, 5}; + Mat input(inShape, matType); + fillRandomSigned(input, matType, backend); + setValueAt(input, 0, minValueAt(matType)); + setValueAt(input, 1, 0); + + Net net; + LayerParams lp; + lp.type = "AbsVal"; + lp.name = "testLayer"; + net.addLayerToPrev(lp.name, lp.type, lp); + + net.setInput(input); + net.setPreferableBackend(backend); + net.setPreferableTarget(target); + + Mat re; + re = net.forward(); + EXPECT_EQ(re.depth(), matType); + ASSERT_EQ(re.total(), input.total()); + + for (int i = 0; i < (int)input.total(); ++i) + { + int64_t value = getValueAt(input, i); + // the minimum has no positive counterpart, so it maps to itself + int64_t expected = value == minValueAt(matType) ? value : (value < 0 ? -value : value); + EXPECT_EQ(getValueAt(re, i), expected) << "index " << i; + } +} + +// Only the OpenCV backend implements the integer kernels. +INSTANTIATE_TEST_CASE_P(/**/, Test_Abs_Int, Combine( + testing::Values(CV_8U, CV_8S, CV_16U, CV_16S, CV_32U, CV_32S, CV_64U, CV_64S), + dnnBackendsAndTargets(false, false, true, false, false, false, false, false) +)); + +typedef testing::TestWithParam > > Test_Sign_Int; +TEST_P(Test_Sign_Int, random) +{ + int matType = get<0>(GetParam()); + tuple backend_target= get<1>(GetParam()); + Backend backend = get<0>(backend_target); + Target target = get<1>(backend_target); + + std::vector inShape{2, 3, 4, 5}; + Mat input(inShape, matType); + fillRandomSigned(input, matType, backend); + setValueAt(input, 0, minValueAt(matType)); + setValueAt(input, 1, 0); + + Net net; + LayerParams lp; + lp.type = "Sign"; + lp.name = "testLayer"; + net.addLayerToPrev(lp.name, lp.type, lp); + + net.setInput(input); + net.setPreferableBackend(backend); + net.setPreferableTarget(target); + + Mat re; + re = net.forward(); + EXPECT_EQ(re.depth(), matType); + ASSERT_EQ(re.total(), input.total()); + + for (int i = 0; i < (int)input.total(); ++i) + { + int64_t value = getValueAt(input, i); + int64_t expected = (value > 0) - (value < 0); + EXPECT_EQ(getValueAt(re, i), expected) << "index " << i; + } +} + +// Only the OpenCV backend implements the integer kernels. +INSTANTIATE_TEST_CASE_P(/**/, Test_Sign_Int, Combine( + testing::Values(CV_8U, CV_8S, CV_16U, CV_16S, CV_32U, CV_32S, CV_64U, CV_64S), + dnnBackendsAndTargets(false, false, true, false, false, false, false, false) +)); + +// ONNX Neg imports as Power with scale=-1, which is the only form of Power that has an +// integer path. +typedef testing::TestWithParam > > Test_Neg_Int; +TEST_P(Test_Neg_Int, random) +{ + int matType = get<0>(GetParam()); + tuple backend_target= get<1>(GetParam()); + Backend backend = get<0>(backend_target); + Target target = get<1>(backend_target); + + std::vector inShape{2, 3, 4, 5}; + Mat input(inShape, matType); + fillRandomSigned(input, matType, backend); + setValueAt(input, 0, 0); + setValueAt(input, 1, minValueAt(matType)); + + Net net; + LayerParams lp; + lp.type = "Power"; + lp.name = "testLayer"; + lp.set("scale", -1); + net.addLayerToPrev(lp.name, lp.type, lp); + + net.setInput(input); + net.setPreferableBackend(backend); + net.setPreferableTarget(target); + + Mat re; + re = net.forward(); + EXPECT_EQ(re.depth(), matType); + ASSERT_EQ(re.total(), input.total()); + + for (int i = 0; i < (int)input.total(); ++i) + { + int64_t value = getValueAt(input, i); + // the minimum has no positive counterpart, so it wraps to itself + int64_t expected = value == minValueAt(matType) ? value : -value; + EXPECT_EQ(getValueAt(re, i), expected) << "index " << i; + } +} + +// Only the OpenCV backend implements the integer kernels. +INSTANTIATE_TEST_CASE_P(/**/, Test_Neg_Int, Combine( + testing::Values(CV_32S, CV_64S), + dnnBackendsAndTargets(false, false, true, false, false, false, false, false) +)); + +typedef testing::TestWithParam > > Test_GatherND_Int; +TEST_P(Test_GatherND_Int, random) +{ + int matType = get<0>(GetParam()); + int indicesType = get<1>(GetParam()); + tuple backend_target= get<2>(GetParam()); + Backend backend = get<0>(backend_target); + Target target = get<1>(backend_target); + + const bool wideIntType = matType == CV_16U || matType == CV_16S || + matType == CV_32U || matType == CV_64U; + if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && wideIntType) + applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH); // OpenVINO carries a tensor type for 8-bit, signed 32/64-bit and float depths only + if (backend == DNN_BACKEND_CUDA && wideIntType) + applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA); // The CUDA wrapper carries a tensor type for 8-bit, signed 32/64-bit and float depths only + if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && matType == CV_64S) + applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH); // There is a problem with OpenVINO and custom int64 layers. After model compilation the output tensor type changes from int64 to int32 + + std::vector inShape{4, 5}; + Mat input(inShape, matType); + fillRandom(input, matType, backend); + + // Each row of indices selects one full row of the input. + std::vector rowPicks{2, 0, 3}; + std::vector idxShape{(int)rowPicks.size(), 1}; + Mat indices(idxShape, indicesType); + for (size_t i = 0; i < rowPicks.size(); ++i) + { + if (indicesType == CV_32S) + indices.ptr()[i] = (int32_t)rowPicks[i]; + else + indices.ptr()[i] = rowPicks[i]; + } + + Net net; + LayerParams lp; + lp.type = "GatherND"; + lp.name = "testLayer"; + int id = net.addLayerToPrev(lp.name, lp.type, lp); + net.connect(0, 1, id, 1); + + std::vector inpNames(2); + inpNames[0] = "gathernd_input"; + inpNames[1] = "gathernd_indices"; + net.setInputsNames(inpNames); + net.setInput(input, inpNames[0]); + net.setInput(indices, inpNames[1]); + + net.setPreferableBackend(backend); + net.setPreferableTarget(target); + + Mat re; + re = net.forward(); + EXPECT_EQ(re.depth(), matType); + ASSERT_EQ(re.size.dims, 2); + ASSERT_EQ(re.size[0], (int)rowPicks.size()); + ASSERT_EQ(re.size[1], input.size[1]); + + std::vector reIndices(2), inIndices(2); + for (int i = 0; i < re.size[0]; ++i) + { + reIndices[0] = i; + inIndices[0] = (int)rowPicks[i]; + for (int j = 0; j < re.size[1]; ++j) + { + reIndices[1] = j; + inIndices[1] = j; + EXPECT_EQ(getValueAt(re, reIndices.data()), getValueAt(input, inIndices.data())); + } + } +} + +INSTANTIATE_TEST_CASE_P(/**/, Test_GatherND_Int, Combine( + testing::Values(CV_Bool, CV_8U, CV_8S, CV_16U, CV_16S, CV_32U, CV_32S, CV_64U, CV_64S), + testing::Values(CV_32S, CV_64S), + dnnBackendsAndTargets() +)); + }} // namespace diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp index 961a58add1..e19d882e1e 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp @@ -2694,7 +2694,7 @@ CASE(test_slice_neg_steps) CASE(test_slice_negative_axes) SKIP; CASE(test_slice_start_out_of_bounds) - // no filter + SKIP; CASE(test_softmax_axis_0) #if SKIP_SET_1 SKIP_OPENCL; diff --git a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp index 82dc196637..d59a7d8876 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp @@ -283,7 +283,6 @@ "test_simple_rnn_batchwise", // Issue:: Parser: Can't create layer "onnx_node_output_1!Y_h" of type "RNN" in function 'getLayerInstance' "test_simple_rnn_defaults", // ---- same as above --- "test_simple_rnn_with_initial_bias", // ---- same as above --- -"test_slice_start_out_of_bounds", "test_split_to_sequence_1", "test_split_to_sequence_2", "test_split_to_sequence_nokeepdims",