Merge pull request #29786 from Prasadayus:layers_dtype_coverage_increase

Extend data type support in elementwise, gather and scatter layers - #29786

### PR Changes:

### Support added, per layer

  | Layer | Types added | Gate / kernel |
  |---|---|---|
  | Abs | 8U, 8S, 16U, 16S, 32U, 32S, 64U, 64S | Gate + new templated integer kernel |
  | Sign | 8U, 8S, 16U, 16S, 32U, 32S, 64U, 64S | Gate + same templated kernel |
  | Neg | 32S | Gate only. The kernel already had a `CV_32S` branch |
  | GatherElements | 64F, 16U, 16S, 32U, 64U | Gate + element-width dispatch |
  | Scatter | 64F, 16U, 16S, 32U, 64U | Gate + dispatch arms, all five reductions checked |
  | ScatterND | 64F, 16U, 16S, 32U, 64U | Gate + dispatch arms, same as Scatter |
  | GatherND | 64F, 16U, 16S, 32U, 64U | Gate + element-width dispatch |
  | Slice2 | (fix) | Kernel. Wrong-width copy, see below |


  `Abs`/`Sign` use one template over all widths with the signed/unsigned split resolved at compile time;
  unsigned `abs` short-circuits to `copyTo` and unsigned `sign` reduces to `x != 0`. `GatherElements` and
  `GatherND` only move elements, so their per-dtype arms collapsed to four widths. `GatherND` also unified a
  target-conditional gate that split `16F`/`32F` by target in a file with no OpenCL path.

  `Slice2` had two duplicated depth chains that both fell through to `run_parallel<float>`, a 4-byte copy, so
  `64F`/`64U` truncated and `16U`/`16S` read and wrote past the element. Reachable only when the innermost axis
  has `step != 1`, which is why the float32 slice tests passed. Now dispatches on `elemSize()`, matching
  `pad2_layer.cpp`.

  **Two further fixes:** signed-overflow UB in the int64 `Power`/`Neg` path (`sp[i] * scale` is undefined at
  `INT64_MIN`, now multiplied through `uint64_t`), and `CV_OCL_RUN` now skips integer depths, since the OCL
  activation kernels are float math and `CV_32S` would have gone through a 24-bit mantissa.

  **New accuracy tests in `test_int.cpp`**: `Test_Abs_Int`, `Test_Sign_Int`, `Test_Neg_Int`, `Test_Scatter_Int`,
  `Test_GatherND_Int`, with `Test_GatherElements_Int` and `Test_ScatterND_Int` widened to nine depths.

  Removed `test_slice_start_out_of_bounds` from the parser denylist.

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Prasad Ayush Kumar
2026-08-27 11:56:14 +05:30
committed by GitHub
parent e13d6719eb
commit 30e2e0aaff
9 changed files with 647 additions and 113 deletions

View File

@@ -52,6 +52,7 @@
#include <opencv2/dnn/shape_utils.hpp>
#include <iostream>
#include <limits>
#include <type_traits>
#include <cfenv>
#ifdef HAVE_OPENCL
@@ -106,13 +107,102 @@ int ActivationLayer::getLayouts(const std::vector<DataLayout>& actualInputs,
}
struct PowerFunctor;
struct AbsValFunctor;
struct SignFunctor;
template<typename Func>
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<typename T> 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<T>::type UT;
return x < 0 ? (T)(UT(0) - (UT)x) : x;
}
template<typename T> static inline T intAbs(T x, std::false_type) { return x; }
template<typename T> static inline T intSign(T x, std::true_type) { return (T)((x > 0) - (x < 0)); }
template<typename T> static inline T intSign(T x, std::false_type) { return (T)(x != 0); }
struct IntAbsOp
{
template<typename T> static inline T apply(T x)
{
return intAbs(x, std::integral_constant<bool, std::numeric_limits<T>::is_signed>());
}
};
struct IntSignOp
{
template<typename T> static inline T apply(T x)
{
return intSign(x, std::integral_constant<bool, std::numeric_limits<T>::is_signed>());
}
};
template<typename Body> 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<typename T, typename Op> static inline void intUnaryKernel(const Mat& src, Mat& dst)
{
const T* srcptr = src.ptr<T>();
T* dstptr = dst.ptr<T>();
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<typename Op> static inline bool intUnaryDispatch(const Mat& src, Mat& dst)
{
if (src.type() != dst.type())
return false;
switch (src.depth())
{
case CV_8U: intUnaryKernel<uint8_t, Op>(src, dst); break;
case CV_8S: intUnaryKernel<int8_t, Op>(src, dst); break;
case CV_16U: intUnaryKernel<uint16_t, Op>(src, dst); break;
case CV_16S: intUnaryKernel<int16_t, Op>(src, dst); break;
case CV_32U: intUnaryKernel<uint32_t, Op>(src, dst); break;
case CV_32S: intUnaryKernel<int32_t, Op>(src, dst); break;
case CV_64U: intUnaryKernel<uint64_t, Op>(src, dst); break;
case CV_64S: intUnaryKernel<int64_t, Op>(src, dst); break;
default: return false;
}
return true;
}
template<typename Func>
class ElementWiseLayer : public Func::Layer
{
@@ -223,11 +313,33 @@ public:
return true;
}
void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs,
const int requiredInternals,
std::vector<MatType>& outputs,
std::vector<MatType>& 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<Func>::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>();
float* dstptr = dst.ptr<float>();
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<AbsValFunctor>
template<>
const char* const AbsValFunctor::BaseDefaultFunctor<AbsValFunctor>::ocl_kernel_name = "AbsValForward";
template<>
struct ElementWiseIntDispatch<AbsValFunctor>
{
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<IntAbsOp>(src, dst);
}
};
struct BNLLFunctor : public BaseDefaultFunctor<BNLLFunctor>
{
typedef BNLLLayer Layer;
@@ -2967,14 +3093,10 @@ struct PowerFunctor : public BaseFunctor
template<>
struct ElementWiseIntDispatch<PowerFunctor>
{
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<PowerFunctor>
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<PowerFunctor>
{
const int64_t* sp = src.ptr<int64_t>();
int64_t* dp = dst.ptr<int64_t>();
// 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<SignFunctor>
template<>
const char* const SignFunctor::BaseDefaultFunctor<SignFunctor>::ocl_kernel_name = "SignForward";
template<>
struct ElementWiseIntDispatch<SignFunctor>
{
static inline bool supports(const SignFunctor&, int depth) { return isIntegerDepth(depth); }
static inline bool apply(const SignFunctor&, const Mat& src, Mat& dst)
{
return intUnaryDispatch<IntSignOp>(src, dst);
}
};
struct ShrinkFunctor : public BaseDefaultFunctor<ShrinkFunctor>
{

View File

@@ -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<int32_t, uchar>(data, indices, out); break;
case CV_8S: forward_impl<int32_t, schar>(data, indices, out); break;
case CV_32S: forward_impl<int32_t, int32_t>(data, indices, out); break;
case CV_16F: forward_impl<int32_t, int16_t>(data, indices, out); break;
case CV_32F: forward_impl<int32_t, float>(data, indices, out); break;
case CV_64F: forward_impl<int32_t, double>(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<int64_t, uchar>(data, indices, out); break;
case CV_8S: forward_impl<int64_t, schar>(data, indices, out); break;
case CV_32S: forward_impl<int64_t, int32_t>(data, indices, out); break;
case CV_16F: forward_impl<int64_t, int16_t>(data, indices, out); break;
case CV_32F: forward_impl<int64_t, float>(data, indices, out); break;
case CV_64F: forward_impl<int64_t, double>(data, indices, out); break;
case CV_64S: forward_impl<int64_t, int64_t>(data, indices, out); break;
default: CV_Error(Error::StsNotImplemented, "Unsupported data type");
}
} break;
case CV_32S: widthDispatch<int32_t>(data, indices, out); break;
case CV_64S: widthDispatch<int64_t>(data, indices, out); break;
default: CV_Error(Error::StsNotImplemented, "Unsupported indices type");
}
}
// Gathering copies elements without interpreting them, so only the width matters.
template <typename iT>
void widthDispatch(const Mat& data, const Mat& indices, Mat& out)
{
switch (data.elemSize()) {
case 1: forward_impl<iT, uint8_t>(data, indices, out); break;
case 2: forward_impl<iT, uint16_t>(data, indices, out); break;
case 4: forward_impl<iT, uint32_t>(data, indices, out); break;
case 8: forward_impl<iT, uint64_t>(data, indices, out); break;
default: CV_Error(Error::StsNotImplemented, "Unsupported data type");
}
}
template <typename iT, typename dT>
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);
}

View File

@@ -67,7 +67,8 @@ public:
std::vector<MatType>& 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<typename T_INDEX, typename... Args>
inline void typeDispatch(const int type, Args&&... args)
{
switch (type)
switch (CV_ELEM_SIZE(type))
{
case CV_Bool:
forward_impl<bool, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_8U:
case 1:
forward_impl<uint8_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_8S:
forward_impl<int8_t, T_INDEX>(std::forward<Args>(args)...);
case 2:
forward_impl<uint16_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_16F:
forward_impl<int16_t, T_INDEX>(std::forward<Args>(args)...);
case 4:
forward_impl<uint32_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_32S:
forward_impl<int32_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_64S:
forward_impl<int64_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_32F:
forward_impl<float, T_INDEX>(std::forward<Args>(args)...);
case 8:
forward_impl<uint64_t, T_INDEX>(std::forward<Args>(args)...);
break;
default:
CV_Error(cv::Error::BadDepth, "DNN/GatherElements: Unsupported type.");

View File

@@ -79,7 +79,8 @@ public:
std::vector<MatType>& 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<int8_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_16U:
reductionDispatch<uint16_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_16S:
reductionDispatch<int16_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_32U:
reductionDispatch<uint32_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_32S:
reductionDispatch<int32_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_64U:
reductionDispatch<uint64_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_64S:
reductionDispatch<int64_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_32F:
reductionDispatch<float, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_64F:
reductionDispatch<double, T_INDEX>(std::forward<Args>(args)...);
break;
default:
CV_Error(cv::Error::BadDepth, "Unsupported type.");
};

View File

@@ -73,7 +73,8 @@ public:
std::vector<MatType>& 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<int8_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_16U:
reductionDispatch<uint16_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_16S:
reductionDispatch<int16_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_32U:
reductionDispatch<uint32_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_32S:
reductionDispatch<int32_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_64U:
reductionDispatch<uint64_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_64S:
reductionDispatch<int64_t, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_32F:
reductionDispatch<float, T_INDEX>(std::forward<Args>(args)...);
break;
case CV_64F:
reductionDispatch<double, T_INDEX>(std::forward<Args>(args)...);
break;
default:
CV_Error(cv::Error::BadDepth, "Unsupported type.");
};

View File

@@ -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<Range>& ranges, const std::vector<int>& steps)
{
if (inp.elemSize() == 1) {
run_parallel<uint8_t>(inp, out, ranges, steps);
} else if (inp.elemSize() == 2) {
run_parallel<uint16_t>(inp, out, ranges, steps);
} else if (inp.elemSize() == 4) {
run_parallel<uint32_t>(inp, out, ranges, steps);
} else {
CV_Assert(inp.elemSize() == 8);
run_parallel<uint64_t>(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<int32_t>(inp, outs[0], ranges, steps_vec);
else if (inp.depth() == CV_64S) run_parallel<int64_t>(inp, outs[0], ranges, steps_vec);
else if (inp.depth() == CV_16F) run_parallel<int16_t>(inp, outs[0], ranges, steps_vec);
else if (inp.depth() == CV_8S) run_parallel<int8_t>(inp, outs[0], ranges, steps_vec);
else if (inp.depth() == CV_8U) run_parallel<uint8_t>(inp, outs[0], ranges, steps_vec);
else if (inp.depth() == CV_Bool) run_parallel<uint8_t>(inp, outs[0], ranges, steps_vec);
else run_parallel<float>(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<UMat>& 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<int32_t>(inp, temp, ranges, steps_vec);
else if (inp.depth() == CV_64S) run_parallel<int64_t>(inp, temp, ranges, steps_vec);
else if (inp.depth() == CV_16F) run_parallel<int16_t>(inp, temp, ranges, steps_vec);
else if (inp.depth() == CV_8S) run_parallel<int8_t>(inp, temp, ranges, steps_vec);
else if (inp.depth() == CV_8U) run_parallel<uint8_t>(inp, temp, ranges, steps_vec);
else if (inp.depth() == CV_Bool) run_parallel<uint8_t>(inp, temp, ranges, steps_vec);
else run_parallel<float>(inp, temp, ranges, steps_vec);
run_parallel_by_width(inp, temp, ranges, steps_vec);
temp.copyTo(outs[0]);
}

View File

@@ -18,8 +18,16 @@ int64_t getValueAt(const Mat &m, const int *indices)
return m.at<uint8_t>(indices);
else if (m.type() == CV_8S)
return m.at<int8_t>(indices);
else if (m.type() == CV_16U)
return m.at<uint16_t>(indices);
else if (m.type() == CV_16S)
return m.at<int16_t>(indices);
else if (m.type() == CV_32U)
return m.at<uint32_t>(indices);
else if (m.type() == CV_32S)
return m.at<int32_t>(indices);
else if (m.type() == CV_64U)
return (int64_t)m.at<uint64_t>(indices);
else if (m.type() == CV_64S)
return m.at<int64_t>(indices);
else
@@ -35,8 +43,16 @@ int64_t getValueAt(const Mat &m, int index)
return m.ptr<uint8_t>()[index];
else if (m.type() == CV_8S)
return m.ptr<int8_t>()[index];
else if (m.type() == CV_16U)
return m.ptr<uint16_t>()[index];
else if (m.type() == CV_16S)
return m.ptr<int16_t>()[index];
else if (m.type() == CV_32U)
return m.ptr<uint32_t>()[index];
else if (m.type() == CV_32S)
return m.ptr<int32_t>()[index];
else if (m.type() == CV_64U)
return (int64_t)m.ptr<uint64_t>()[index];
else if (m.type() == CV_64S)
return m.ptr<int64_t>()[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<bool>()[index] = (bool)value;
else if (m.type() == CV_8U)
m.ptr<uint8_t>()[index] = (uint8_t)value;
else if (m.type() == CV_8S)
m.ptr<int8_t>()[index] = (int8_t)value;
else if (m.type() == CV_16U)
m.ptr<uint16_t>()[index] = (uint16_t)value;
else if (m.type() == CV_16S)
m.ptr<int16_t>()[index] = (int16_t)value;
else if (m.type() == CV_32U)
m.ptr<uint32_t>()[index] = (uint32_t)value;
else if (m.type() == CV_32S)
m.ptr<int32_t>()[index] = (int32_t)value;
else if (m.type() == CV_64U)
m.ptr<uint64_t>()[index] = (uint64_t)value;
else if (m.type() == CV_64S)
m.ptr<int64_t>()[index] = value;
else
CV_Error(Error::BadDepth, "Unsupported type");
}
int64_t minValueAt(int matType)
{
if (matType == CV_8S)
return std::numeric_limits<int8_t>::min();
else if (matType == CV_16S)
return std::numeric_limits<int16_t>::min();
else if (matType == CV_32S)
return std::numeric_limits<int32_t>::min();
else if (matType == CV_64S)
return std::numeric_limits<int64_t>::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<tuple<int, tuple<Backend, Target> > > 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<int> 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<int32_t>()[i] = updatesValues[i];
else if (matType == CV_64S)
updates.ptr<int64_t>()[i] = updatesValues[i];
else if (matType == CV_8S)
updates.ptr<int8_t>()[i] = updatesValues[i];
else if (matType == CV_8U)
updates.ptr<uint8_t>()[i] = updatesValues[i];
else if (matType == CV_Bool)
updates.ptr<bool>()[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<tuple<int, int, tuple<Backend, Target> > > Test_Scatter_Int;
TEST_P(Test_Scatter_Int, random)
{
int matType = get<0>(GetParam());
int indicesType = get<1>(GetParam());
tuple<Backend, Target> 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<int> 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<int> 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<int32_t>()[i] = scatterAt;
else
indices.ptr<int64_t>()[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<String> 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<int> 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<int> 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<tuple<int, tuple<Backend, Target> > > Test_Abs_Int;
TEST_P(Test_Abs_Int, random)
{
int matType = get<0>(GetParam());
tuple<Backend, Target> backend_target= get<1>(GetParam());
Backend backend = get<0>(backend_target);
Target target = get<1>(backend_target);
std::vector<int> 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<tuple<int, tuple<Backend, Target> > > Test_Sign_Int;
TEST_P(Test_Sign_Int, random)
{
int matType = get<0>(GetParam());
tuple<Backend, Target> backend_target= get<1>(GetParam());
Backend backend = get<0>(backend_target);
Target target = get<1>(backend_target);
std::vector<int> 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<tuple<int, tuple<Backend, Target> > > Test_Neg_Int;
TEST_P(Test_Neg_Int, random)
{
int matType = get<0>(GetParam());
tuple<Backend, Target> backend_target= get<1>(GetParam());
Backend backend = get<0>(backend_target);
Target target = get<1>(backend_target);
std::vector<int> 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<tuple<int, int, tuple<Backend, Target> > > Test_GatherND_Int;
TEST_P(Test_GatherND_Int, random)
{
int matType = get<0>(GetParam());
int indicesType = get<1>(GetParam());
tuple<Backend, Target> 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<int> inShape{4, 5};
Mat input(inShape, matType);
fillRandom(input, matType, backend);
// Each row of indices selects one full row of the input.
std::vector<int64_t> rowPicks{2, 0, 3};
std::vector<int> idxShape{(int)rowPicks.size(), 1};
Mat indices(idxShape, indicesType);
for (size_t i = 0; i < rowPicks.size(); ++i)
{
if (indicesType == CV_32S)
indices.ptr<int32_t>()[i] = (int32_t)rowPicks[i];
else
indices.ptr<int64_t>()[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<String> 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<int> 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

View File

@@ -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;

View File

@@ -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",