Merge pull request #29360 from abhishek-gola:exotic_cast_operations

Support ONNX Cast/CastLike for FP8/FP4/INT4/UINT4/E8M0 dtypes - #29360

### 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:
Abhishek Gola
2026-08-31 11:35:29 +05:30
committed by GitHub
parent 1e0a222d11
commit 0627765f01
12 changed files with 645 additions and 226 deletions

View File

@@ -8,6 +8,9 @@
#include "layers_common.hpp"
#include "../net_impl.hpp"
#include "opencv-onnx.pb.h"
#include "../onnx/onnx_dtype_convert.hpp"
namespace cv { namespace dnn {
// ONNX Cast operator
@@ -19,13 +22,18 @@ namespace cv { namespace dnn {
namespace
{
// ONNX Cast float->int truncates toward zero; Mat::convertTo rounds. Truncate to match the spec.
template<typename DT>
inline void truncateToIntImpl(const Mat& src, Mat& dst)
{
const int n = (int)src.total() * src.channels();
DT* d = dst.ptr<DT>();
if (src.depth() == CV_32F)
if (src.depth() == CV_16F)
{
const hfloat* s = src.ptr<hfloat>();
for (int i = 0; i < n; ++i)
d[i] = saturate_cast<DT>(std::trunc((float)s[i]));
}
else if (src.depth() == CV_32F)
{
const float* s = src.ptr<float>();
for (int i = 0; i < n; ++i)
@@ -53,87 +61,6 @@ namespace
}
}
inline void castQuantized(const Mat& src, Mat& dst, int targetDepth)
{
if (targetDepth == CV_16F)
{
CV_Assert(dst.depth() == CV_32F);
if (src.depth() == CV_32F)
{
MatConstIterator_<float> sIt = src.begin<float>(), sEnd = src.end<float>();
MatIterator_<float> dIt = dst.begin<float>();
for (; sIt != sEnd; ++sIt, ++dIt)
{
*dIt = (float)hfloat(*sIt);
}
}
else if (src.depth() == CV_64F)
{
MatConstIterator_<double> sIt = src.begin<double>(), sEnd = src.end<double>();
MatIterator_<float> dIt = dst.begin<float>();
for (; sIt != sEnd; ++sIt, ++dIt)
{
float v = (float)*sIt;
*dIt = (float)hfloat(v);
}
}
else
{
Mat src32; src.convertTo(src32, CV_32F);
MatConstIterator_<float> sIt = src32.begin<float>(), sEnd = src32.end<float>();
MatIterator_<float> dIt = dst.begin<float>();
for (; sIt != sEnd; ++sIt, ++dIt)
{
*dIt = (float)hfloat(*sIt);
}
}
return;
}
if (targetDepth == CV_16BF)
{
const int ddepth = dst.depth();
if (!(ddepth == CV_16BF || ddepth == CV_16U))
{
CV_Error(Error::StsNotImplemented, "Unsupported destination depth for BF16 cast");
}
Mat dst_bits(dst.size(), CV_MAKETYPE(CV_16U, dst.channels()), dst.data, dst.step);
const Mat* src32p;
Mat src32;
if (src.depth() == CV_32F)
src32p = &src;
else
{
src.convertTo(src32, CV_32F);
src32p = &src32;
}
const int rows = src32p->rows;
const int cols_x_cn = src32p->cols * src32p->channels();
for (int r = 0; r < rows; ++r)
{
const float* in = src32p->ptr<float>(r);
ushort* out = dst_bits.ptr<ushort>(r);
for (int i = 0; i < cols_x_cn; ++i)
{
// float32 -> bfloat16 with round-to-nearest-even (matches ONNX).
Cv32suf u; u.f = in[i];
const uint32_t x = u.u;
if ((x & 0x7fffffffu) > 0x7f800000u) // NaN: keep it NaN
out[i] = (ushort)((x >> 16) | 0x0040u);
else
{
const uint32_t bias = 0x7fffu + ((x >> 16) & 1u);
out[i] = (ushort)((x + bias) >> 16);
}
}
}
return;
}
src.convertTo(dst, dst.depth());
}
}
class Cast2LayerImpl CV_FINAL : public Cast2Layer
@@ -144,10 +71,13 @@ public:
setParamsFrom(params);
hasToParam = false;
toCvDepth_ = -1;
toOnnxType_ = -1;
saturate_ = params.get<int>("saturate", 1) != 0;
if (params.has("to"))
{
hasToParam = true;
toCvDepth_ = mapToCvDepth(params.get<int>("to"));
toOnnxType_ = params.get<int>("to");
toCvDepth_ = mapToCvDepth(toOnnxType_);
}
else if (params.has("outputType"))
{
@@ -167,6 +97,9 @@ public:
virtual bool supportBackend(int backendId) CV_OVERRIDE
{
// Exotic dtypes (FP8/FP4/INT4/UINT4) are handled on the CPU path only.
if (onnx_dtype::isExotic(toOnnxType_))
return backendId == DNN_BACKEND_OPENCV;
return backendId == DNN_BACKEND_OPENCV ||
backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH;
}
@@ -181,6 +114,18 @@ public:
return false;
}
// Half targets are stored as FP32 unless native FP16 is enabled; forward() still rounds.
int resolveStorageDepth(int targetDepth, bool exotic) const
{
if (!exotic && (targetDepth == CV_16F || targetDepth == CV_16BF))
{
Net::Impl* ni = getNetImpl(const_cast<Cast2LayerImpl*>(this));
if (ni && !ni->enableFP16)
return CV_32F;
}
return targetDepth;
}
virtual void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs,
const int requiredInternals,
@@ -216,9 +161,8 @@ public:
const int in0Type = inputs[0];
const int in0CN = in0Type >= 0 ? CV_MAT_CN(in0Type) : 1;
int planDepth = targetDepth;
if (planDepth == CV_16F) planDepth = CV_32F;
const int outType = CV_MAKETYPE(planDepth, in0CN);
const bool exotic = hasToParam && onnx_dtype::isExotic(toOnnxType_);
const int outType = CV_MAKETYPE(resolveStorageDepth(targetDepth, exotic), in0CN);
outputs.assign(1, outType);
}
@@ -227,6 +171,9 @@ public:
{
std::vector<UMat> inputs, outputs;
if (hasToParam && onnx_dtype::isExotic(toOnnxType_))
return false; // exotic conversions run on the CPU path
inputs_.getUMatVector(inputs);
outputs_.getUMatVector(outputs);
CV_CheckEQ(inputs.size(), (size_t)1, "");
@@ -305,46 +252,42 @@ public:
}
CV_CheckGE(runtimeTargetDepth, 0, "Cast: failed to resolve target data type at runtime");
int plannedDDepth = (runtimeTargetDepth == CV_16F) ? CV_32F : runtimeTargetDepth;
if (dst0.depth() != plannedDDepth)
dst0.create(dst0.size(), CV_MAKETYPE(plannedDDepth, src0.channels()));
const bool exotic = hasToParam && onnx_dtype::isExotic(toOnnxType_);
const int storeDepth = resolveStorageDepth(runtimeTargetDepth, exotic);
if (dst0.depth() != storeDepth)
dst0.create(dst0.size(), CV_MAKETYPE(storeDepth, src0.channels()));
Mat src = src0;
Mat dst = dst0;
if (exotic)
{
castExotic(src, dst, toOnnxType_, saturate_);
return;
}
// Cast to half yields half-representable values even when FP32 carries them.
if (storeDepth != runtimeTargetDepth &&
(runtimeTargetDepth == CV_16F || runtimeTargetDepth == CV_16BF))
{
Mat half;
src.convertTo(half, runtimeTargetDepth);
half.convertTo(dst, storeDepth);
return;
}
const int sdepth = src.depth();
const int ddepth = dst.depth();
if (sdepth == runtimeTargetDepth && !(runtimeTargetDepth == CV_16F && ddepth == CV_32F))
if (sdepth == ddepth && sdepth == runtimeTargetDepth)
{
src0.copyTo(dst0);
return;
}
if (runtimeTargetDepth == CV_16BF && (ddepth == CV_16BF || ddepth == CV_16U))
if ((sdepth == CV_16F || sdepth == CV_32F || sdepth == CV_64F) && CV_IS_INT_TYPE(ddepth))
{
castQuantized(src, dst, CV_16BF);
}
else if (sdepth == CV_16BF)
{
src.convertTo(dst, ddepth);
}
else if (runtimeTargetDepth == CV_16F && ddepth == CV_32F)
{
castQuantized(src, dst, CV_16F);
}
else if (runtimeTargetDepth == CV_64F && ddepth != CV_64F)
{
if (ddepth == CV_16U || ddepth == CV_16BF)
{
castQuantized(src, dst, CV_16BF);
}
else
src.convertTo(dst, ddepth);
}
else if ((sdepth == CV_32F || sdepth == CV_64F) && CV_IS_INT_TYPE(ddepth))
{
truncateFloatToInt(src, dst);
truncateFloatToInt(src, dst); // ONNX float->int truncates toward zero
}
else
{
@@ -374,9 +317,67 @@ public:
}
#endif // HAVE_DNN_NGRAPH
void castExotic(const Mat& src, Mat& dst, int onnxType, bool saturate)
{
const int sdepth = src.depth();
const float* sf = (sdepth == CV_32F) ? src.ptr<float>() : nullptr;
const hfloat* sh = (sdepth == CV_16F) ? src.ptr<hfloat>() : nullptr;
Mat src32;
if (!sf && !sh) { src.convertTo(src32, CV_32F); sf = src32.ptr<float>(); }
const size_t total = src.total() * src.channels();
#define CV_DNN_SRC_F(i) (sf ? sf[i] : (float)sh[i])
if (onnx_dtype::isFp8(onnxType))
{
const onnx_dtype::Fp8Fmt fmt = onnx_dtype::fp8FmtFor(onnxType);
const int ddepth = dst.depth();
if (ddepth == CV_8F_E4M3FN || ddepth == CV_8F_E4M3FNUZ)
{
// Store the ONNX-encoded byte: core's E4M3 encode rounds differently.
uchar* d = dst.ptr<uchar>();
for (size_t i = 0; i < total; i++)
d[i] = onnx_dtype::f32ToFp8(CV_DNN_SRC_F(i), fmt, saturate);
}
else
{
// E5M2/E5M2FNUZ have no native depth: round onto the FP8 grid, keep CV_16F.
hfloat* d = dst.ptr<hfloat>();
for (size_t i = 0; i < total; i++)
d[i] = hfloat(onnx_dtype::fp8ToF32(onnx_dtype::f32ToFp8(CV_DNN_SRC_F(i), fmt, saturate), fmt));
}
}
else if (onnxType == onnx_dtype::ONNX_FLOAT8E8M0)
{
float* d = dst.ptr<float>(); // E8M0 range exceeds FP16, stays CV_32F
for (size_t i = 0; i < total; i++)
d[i] = onnx_dtype::e8m0ToF32(onnx_dtype::f32ToE8M0(CV_DNN_SRC_F(i)));
}
else if (onnxType == opencv_onnx::TensorProto_DataType_FLOAT4E2M1)
{
hfloat* d = dst.ptr<hfloat>();
for (size_t i = 0; i < total; i++)
d[i] = hfloat(onnx_dtype::fp4ToF32(onnx_dtype::f32ToFp4(CV_DNN_SRC_F(i))));
}
else if (onnx_dtype::isInt4(onnxType))
{
schar* d = dst.ptr<schar>();
for (size_t i = 0; i < total; i++)
d[i] = onnx_dtype::f32ToInt4(CV_DNN_SRC_F(i));
}
else // UINT4
{
uchar* d = dst.ptr<uchar>();
for (size_t i = 0; i < total; i++)
d[i] = onnx_dtype::f32ToUint4(CV_DNN_SRC_F(i));
}
#undef CV_DNN_SRC_F
}
private:
bool hasToParam = false;
int toCvDepth_ = -1;
int toOnnxType_ = -1;
bool saturate_ = true;
// ONNX TensorProto::DataType values (see opencv-onnx.proto); the 'to'
// attribute stores the raw ONNX value. Fixed by the ONNX specification,
@@ -398,6 +399,11 @@ private:
static int mapToCvDepth(int v)
{
if (v == onnx_dtype::ONNX_FLOAT8E8M0) return CV_32F; // range exceeds FP16
if (onnx_dtype::isFp8Native(v)) return onnx_dtype::fp8NativeDepth(v); // E4M3FN/E4M3FNUZ
if (onnx_dtype::isExoticFloat(v)) return CV_16F; // E5M2/FP4
if (onnx_dtype::isInt4(v)) return CV_8S;
if (onnx_dtype::isUint4(v)) return CV_8U;
switch (v)
{
case ONNX_DT_FLOAT: return CV_32F;

View File

@@ -69,6 +69,14 @@ public:
have_bias = params.get<bool>("have_bias", false);
real_ndims_C = params.get<int>("real_ndims_C", -1);
for (Mat& blob : blobs) {
if (blob.type() == CV_16F || blob.type() == CV_16BF) {
Mat widened;
blob.convertTo(widened, CV_32F);
blob = widened;
}
}
}
virtual bool supportBackend(int backendId) CV_OVERRIDE {

View File

@@ -42,6 +42,14 @@ class MatMulLayerImpl CV_FINAL : public MatMulLayer {
beta = params.get<float>("beta", 1.f);
real_ndims_C = params.get<int>("real_ndims_C", -1);
for (Mat& blob : blobs) {
if (blob.type() == CV_16F || blob.type() == CV_16BF) {
Mat widened;
blob.convertTo(widened, CV_32F);
blob = widened;
}
}
}
virtual bool supportBackend(int backendId) CV_OVERRIDE {

View File

@@ -139,6 +139,7 @@ struct Net::Impl : public detail::NetImplBase
KVCacheManager kvCacheManager;
Ptr<Graph> mainGraph;
std::vector<int> mainGraphOutTypes;
int globGraphIdx;
int accuracy;
@@ -551,6 +552,8 @@ struct Net::Impl : public detail::NetImplBase
void fuseScaleSoftmax();
// replace constant sub-expressions with their results
// widen FP16/BF16 constants to execution precision while the engine lacks half kernels
void widenHalfConstants();
void fuseQDQ();
void constFold();
// make some operations (activation, batch norm, convolution) unary if

View File

@@ -552,6 +552,29 @@ Ptr<Graph> Net::Impl::newGraph(const std::string& name_, const std::vector<Arg>&
return graph;
}
// No half kernels yet, so half constants are widened just as setGraphInput() widens inputs.
void Net::Impl::widenHalfConstants()
{
if (enableFP16)
return;
size_t nargs = args.size();
__tensors__.resize(nargs);
for (size_t i = 1; i < nargs; i++) {
ArgData& adata = args[i];
if (adata.kind != DNN_ARG_CONST ||
(adata.type != CV_16F && adata.type != CV_16BF))
continue;
Mat& t = __tensors__[i];
if (!t.empty()) {
Mat widened;
widened.fit(t.shape(), accuracy);
t.convertTo(widened, accuracy);
t = widened;
}
adata.type = accuracy;
}
}
void Net::Impl::prepareForInference()
{
#ifdef HAVE_ONNXRUNTIME
@@ -563,6 +586,7 @@ void Net::Impl::prepareForInference()
#endif
if (!prepared) {
widenHalfConstants();
fuseQDQ();
constFold();
fuseBN();
@@ -1266,11 +1290,10 @@ void Net::Impl::setGraphInput(Ptr<Graph>& graph, size_t idx, const Mat& m)
if ((adata_type == CV_16F || adata_type == CV_16BF) && !enableFP16)
adata_type = CV_32F;
if (adata_type != mtype &&
!((adata_type == CV_64F || adata_type == CV_32F || adata_type == CV_16F || adata_type == CV_16BF) &&
(mtype == CV_64F || mtype == CV_32F || mtype == CV_16F || mtype == CV_16BF)) &&
!((adata_type == CV_8U || adata_type == CV_8S || adata_type == CV_16U || adata_type == CV_16S || adata_type == CV_32S || adata_type == CV_32U || adata_type == CV_64S || adata_type == CV_64U) &&
(mtype == CV_8U || mtype == CV_8S || mtype == CV_16U || mtype == CV_16S || mtype == CV_32S || mtype == CV_32U || mtype == CV_64S || mtype == CV_64U)) &&
// setInput converts to the declared type, so any numeric source type is acceptable.
const bool aNumeric = CV_IS_INT_TYPE(adata_type) || CV_IS_FLOAT_TYPE(adata_type);
const bool mNumeric = CV_IS_INT_TYPE(mtype) || CV_IS_FLOAT_TYPE(mtype);
if (adata_type != mtype && !(aNumeric && mNumeric) &&
!(adata.type == CV_16BF && mtype == CV_16U) && !(adata.type == CV_16F && mtype == CV_16U) &&
!m.empty())
{
@@ -1868,6 +1891,18 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
outputsVec[i].fit(outm.shape(), outm.type());
outm.copyTo(outputsVec[i]);
}
// Narrow to the declared output dtype when an op computed in a wider type.
// Half is excepted: the graph was widened, so narrowing would only lose precision.
int declaredOutType = i < mainGraphOutTypes.size() ? mainGraphOutTypes[i] : -1;
if (!enableFP16 && (declaredOutType == CV_16F || declaredOutType == CV_16BF))
declaredOutType = -1;
if (declaredOutType >= 0 && !outputsVec[i].empty() &&
outputsVec[i].depth() != CV_MAT_DEPTH(declaredOutType))
{
Mat tmp;
outputsVec[i].convertTo(tmp, CV_MAT_DEPTH(declaredOutType));
outputsVec[i] = tmp;
}
} else {
outputsVec[i] = outm;
}

View File

@@ -0,0 +1,188 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (C) 2026, BigVision LLC, all rights reserved.
// Third party copyrights are property of their respective owners.
// TODO: drop when these dtypes get native cv::Mat depths; math then moves to core.
#ifndef OPENCV_DNN_ONNX_DTYPE_CONVERT_HPP
#define OPENCV_DNN_ONNX_DTYPE_CONVERT_HPP
#include <opencv2/core.hpp>
#include <cstdint>
#include <cmath>
#include <limits>
namespace cv { namespace dnn { namespace onnx_dtype {
// ONNX TensorProto.DataType value for FLOAT8E8M0 (absent from the bundled opencv-onnx.proto).
enum { ONNX_FLOAT8E8M0 = 24 };
// Description of a sign+exponent+mantissa low-precision float (the FP8 family).
struct Fp8Fmt { int ebits, mbits, bias; bool has_inf, fnuz; };
// Returns the format for an FP8 ONNX dtype, or {0,...} for non-FP8 types.
inline Fp8Fmt fp8FmtFor(int onnxType)
{
switch (onnxType)
{
case 17: return {4, 3, 7, false, false}; // FLOAT8E4M3FN
case 18: return {4, 3, 8, false, true}; // FLOAT8E4M3FNUZ
case 19: return {5, 2, 15, true, false}; // FLOAT8E5M2
case 20: return {5, 2, 16, false, true}; // FLOAT8E5M2FNUZ
}
return {0, 0, 0, false, false};
}
inline bool isFp8(int onnxType) { return onnxType >= 17 && onnxType <= 20; }
// E4M3FN (17) and E4M3FNUZ (18) have a native cv::Mat depth; E5M2/E5M2FNUZ do not.
inline bool isFp8Native(int t) { return t == 17 || t == 18; }
inline int fp8NativeDepth(int t) { return t == 17 ? CV_8F_E4M3FN : CV_8F_E4M3FNUZ; }
// Storage: E4M3 native FP8; E5M2/FP4 CV_16F; E8M0 CV_32F; INT4 CV_8S; UINT4 CV_8U.
inline bool isExoticFloat(int t) { return isFp8(t) || t == 23 /*FP4*/ || t == ONNX_FLOAT8E8M0; }
inline bool isInt4(int t) { return t == 22; }
inline bool isUint4(int t) { return t == 21; }
inline bool isExotic(int t) { return isExoticFloat(t) || isInt4(t) || isUint4(t); }
inline uint32_t f2u(float f) { Cv32suf s; s.f = f; return s.u; }
// Round 'full' dropping 'shift' low bits, round to nearest, ties to even.
inline uint32_t roundRNE(uint32_t full, int shift)
{
if (shift <= 0) return full << (-shift);
uint32_t q = full >> shift;
uint32_t rem = full & ((1u << shift) - 1);
uint32_t half = 1u << (shift - 1);
if (rem > half || (rem == half && (q & 1))) q++;
return q;
}
inline uint8_t f32ToFp8(float x, const Fp8Fmt& f, bool saturate)
{
uint32_t u = f2u(x), sign = (u >> 31) & 1, e = (u >> 23) & 0xFF, m = u & 0x7FFFFF;
const int W = f.ebits + f.mbits;
const uint32_t sbit = sign << W, maxe = (1u << f.ebits) - 1;
const uint8_t NaNc = f.fnuz ? 0x80 : (uint8_t)(sbit | (maxe << f.mbits) | ((1u << f.mbits) - 1));
uint8_t maxfin;
if (f.has_inf) maxfin = (uint8_t)(sbit | ((maxe - 1) << f.mbits) | ((1u << f.mbits) - 1));
else if (f.fnuz) maxfin = (uint8_t)(sbit | (maxe << f.mbits) | ((1u << f.mbits) - 1));
else maxfin = (uint8_t)(sbit | (maxe << f.mbits) | ((1u << f.mbits) - 2));
const uint8_t Infc = (uint8_t)(sbit | (maxe << f.mbits));
if (e == 0xFF && m != 0) return NaNc;
if (e == 0xFF && m == 0)
{
if (f.has_inf && !f.fnuz) return saturate ? maxfin : Infc;
return saturate ? maxfin : NaNc;
}
if (e == 0 && m == 0) return f.fnuz ? 0 : (uint8_t)sbit;
int newexp = (int)e - 127 + f.bias;
const uint32_t full = (1u << 23) | m;
if (newexp <= 0)
{
const int shift = (23 - f.mbits) + (1 - newexp);
uint32_t mant = (shift >= 32) ? 0u : roundRNE(full, shift);
if (mant == 0) return f.fnuz ? 0 : (uint8_t)sbit;
return (uint8_t)(sbit | mant);
}
uint32_t rounded = roundRNE(full, 23 - f.mbits);
if (rounded & (1u << (f.mbits + 1))) { rounded >>= 1; newexp++; }
const uint32_t mant = rounded & ((1u << f.mbits) - 1);
bool ov;
if (f.has_inf) ov = (uint32_t)newexp >= maxe;
else if (f.fnuz) ov = (uint32_t)newexp > maxe;
else ov = (uint32_t)newexp > maxe || ((uint32_t)newexp == maxe && mant == (1u << f.mbits) - 1);
if (ov)
{
if (f.has_inf && !f.fnuz) return saturate ? maxfin : Infc;
return saturate ? maxfin : NaNc;
}
return (uint8_t)(sbit | ((uint32_t)newexp << f.mbits) | mant);
}
// ONNX preserves the NaN sign bit (FNUZ 0x80 decodes to -NaN); bit-exact tests need it.
inline float signedQNaN(uint32_t sign) { Cv32suf s; s.u = sign ? 0xFFC00000u : 0x7FC00000u; return s.f; }
inline float fp8ToF32(uint8_t code, const Fp8Fmt& f)
{
const int W = f.ebits + f.mbits;
const uint32_t sign = (code >> W) & 1;
const uint32_t exp = ((uint32_t)code >> f.mbits) & ((1u << f.ebits) - 1);
const uint32_t man = code & ((1u << f.mbits) - 1);
const uint32_t maxe = (1u << f.ebits) - 1;
const float s = sign ? -1.f : 1.f;
if (f.fnuz)
{
if ((uint32_t)code == (1u << W)) return signedQNaN(sign);
}
else if (exp == maxe)
{
if (f.has_inf) return man == 0 ? s * std::numeric_limits<float>::infinity() : signedQNaN(sign);
if (man == (1u << f.mbits) - 1) return signedQNaN(sign);
}
if (exp == 0) return s * (float)man * std::ldexp(1.0f, 1 - f.bias - f.mbits);
return s * (1.0f + (float)man / (float)(1u << f.mbits)) * std::ldexp(1.0f, (int)exp - f.bias);
}
// E8M0: unsigned, 8-bit exponent only (no sign, no mantissa), bias 127; 0xFF == NaN.
// Conversion rounds UP (ceil to next power of two), per the ONNX reference data.
inline uint8_t f32ToE8M0(float x)
{
if (std::isnan(x)) return 0xFF;
const uint32_t u = f2u(x), sign = (u >> 31) & 1, e = (u >> 23) & 0xFF, m = u & 0x7FFFFF;
if (x == 0.f) return 0;
if (sign) return 0xFF; // negative -> NaN (unsigned format)
if (e == 0xFF) return 0xFF; // inf -> NaN
int code = (int)e + (m > 0 ? 1 : 0);
return (uint8_t)(code > 0xFE ? 0xFE : code);
}
inline float e8m0ToF32(uint8_t c)
{
if (c == 0xFF) return std::numeric_limits<float>::quiet_NaN();
return std::ldexp(1.0f, (int)c - 127);
}
// FLOAT4E2M1: 1-2-1, bias 1. Magnitudes {0,.5,1,1.5,2,3,4,6}; saturating; NaN -> 0x8.
inline const float* fp4Values()
{
static const float v[8] = {0.f, 0.5f, 1.f, 1.5f, 2.f, 3.f, 4.f, 6.f};
return v;
}
inline uint8_t f32ToFp4(float x)
{
if (std::isnan(x)) return 0x8;
const uint32_t sign = (f2u(x) >> 31) & 1;
const float ax = std::fabs(x);
const float* V = fp4Values();
int best;
if (std::isinf(ax) || ax >= 6.0f) best = 7; // saturate to max magnitude
else
{
best = 0; float bestd = std::numeric_limits<float>::max();
for (int c = 0; c < 8; c++)
{
const float d = std::fabs(ax - V[c]);
if (d < bestd) { bestd = d; best = c; }
else if (d == bestd && (best & 1)) best = c; // tie -> even code
}
}
return (uint8_t)((sign << 3) | best);
}
inline float fp4ToF32(uint8_t code) { return (code & 0x8 ? -1.f : 1.f) * fp4Values()[code & 7]; }
// INT4/UINT4: truncate toward zero, keep low 4 bits (wraps; ONNX does not clamp).
inline int8_t f32ToInt4(float x) { int n = (int)std::trunc(x) & 0xF; return (int8_t)(n >= 8 ? n - 16 : n); }
inline uint8_t f32ToUint4(float x) { return (uint8_t)((int)std::trunc(x) & 0xF); }
inline int8_t int4SignExtend(uint8_t nib) { return (int8_t)((nib & 0xF) >= 8 ? (int)(nib & 0xF) - 16 : (nib & 0xF)); }
// Extract the i-th 4-bit element from a tensor packed two-per-byte (low nibble first).
inline uint8_t unpackNibble(const uchar* raw, size_t i) { return (i & 1) ? (raw[i >> 1] >> 4) & 0xF : raw[i >> 1] & 0xF; }
}}} // namespace cv::dnn::onnx_dtype
#endif // OPENCV_DNN_ONNX_DTYPE_CONVERT_HPP

View File

@@ -10,6 +10,7 @@
#ifdef HAVE_PROTOBUF
#include "../graph_simplifier.hpp"
#include "onnx_graph_simplifier.hpp"
#include "onnx_dtype_convert.hpp"
#include <opencv2/core/utils/filesystem.hpp>
#include "opencv2/core/utils/filesystem.private.hpp"
@@ -1958,7 +1959,12 @@ int dataType2cv(int dt)
dt == opencv_onnx::TensorProto_DataType_BFLOAT16 ? CV_16BF :
dt == opencv_onnx::TensorProto_DataType_COMPLEX64 ? CV_32FC2 :
dt == opencv_onnx::TensorProto_DataType_COMPLEX128 ? CV_64FC2 :
dt == opencv_onnx::TensorProto_DataType_BOOL ? CV_Bool : -1;
dt == opencv_onnx::TensorProto_DataType_BOOL ? CV_Bool :
dt == opencv_onnx::TensorProto_DataType_UINT4 ? CV_8U :
dt == opencv_onnx::TensorProto_DataType_INT4 ? CV_8S :
dt == onnx_dtype::ONNX_FLOAT8E8M0 ? CV_32F :
onnx_dtype::isFp8Native(dt) ? onnx_dtype::fp8NativeDepth(dt) :
onnx_dtype::isExoticFloat(dt) ? CV_16F : -1;
}
Mat getMatFromTensor(const opencv_onnx::TensorProto& tensor_proto, bool uint8ToInt8, const std::string base_path)
@@ -2029,33 +2035,23 @@ Mat getMatFromTensor(const opencv_onnx::TensorProto& tensor_proto, bool uint8ToI
}
else if (datatype == opencv_onnx::TensorProto_DataType_FLOAT16)
{
// FIXME, for now, we only load FP16 Tensor as FP32 Mat, full support for FP16 is required in the future.
CV_LOG_ONCE_INFO(NULL, "DNN: load FP16 model as FP32 model, and it takes twice the FP16 RAM requirement.");
// ONNX saves float 16 data in two format: int32 and raw_data.
// Load FP16 natively as CV_16F; ONNX stores it in int32_data or raw_data.
// Link: https://github.com/onnx/onnx/issues/4460#issuecomment-1224373746
if (!tensor_proto.int32_data().empty())
{
size_t sz = tensor_proto.int32_data().size();
checkPayloadSize(sz);
std::vector<int16_t> halfvec(sz);
blob.create((int)sizes.size(), sizes.data(), CV_16FC1);
const int32_t* intdata = (const int32_t*)tensor_proto.int32_data().data();
uint16_t* dst = (uint16_t*)blob.data;
for (size_t i = 0; i < sz; i++)
{
union
{
int16_t h;
int32_t i;
} u;
u.i = intdata[i];
halfvec[i] = u.h;
}
Mat(sizes, CV_16FC1, halfvec.data()).convertTo(blob, CV_32FC1);
dst[i] = (uint16_t)(intdata[i] & 0xFFFF);
}
else
{
checkPayloadSize(raw_data_size / sizeof(int16_t));
Mat(sizes, CV_16FC1, rawdata).convertTo(blob, CV_32FC1);
Mat(sizes, CV_16FC1, rawdata).copyTo(blob);
}
}
else if (datatype == opencv_onnx::TensorProto_DataType_BFLOAT16)
@@ -2236,6 +2232,58 @@ Mat getMatFromTensor(const opencv_onnx::TensorProto& tensor_proto, bool uint8ToI
else
Mat(sizes, CV_64UC1, rawdata).copyTo(blob);
}
else if (datatype == opencv_onnx::TensorProto_DataType_FLOAT8E4M3FN ||
datatype == opencv_onnx::TensorProto_DataType_FLOAT8E4M3FNUZ)
{
// E4M3FN/E4M3FNUZ have a native depth: keep the raw FP8 bytes.
checkPayloadSize(raw_data_size);
blob.create((int)sizes.size(), sizes.data(),
CV_MAKETYPE(onnx_dtype::fp8NativeDepth(datatype), 1));
memcpy(blob.data, rawdata, (size_t)blob.total() * blob.elemSize());
}
else if (datatype == opencv_onnx::TensorProto_DataType_FLOAT8E5M2 ||
datatype == opencv_onnx::TensorProto_DataType_FLOAT8E5M2FNUZ)
{
// E5M2/E5M2FNUZ have no native depth: decode losslessly into CV_16F.
const onnx_dtype::Fp8Fmt fmt = onnx_dtype::fp8FmtFor(datatype);
blob.create((int)sizes.size(), sizes.data(), CV_16FC1);
const uchar* src = (const uchar*)rawdata;
hfloat* dst = blob.ptr<hfloat>();
for (size_t i = 0, total = blob.total(); i < total; i++)
dst[i] = hfloat(onnx_dtype::fp8ToF32(src[i], fmt));
}
else if (datatype == onnx_dtype::ONNX_FLOAT8E8M0)
{
blob.create((int)sizes.size(), sizes.data(), CV_32FC1);
const uchar* src = (const uchar*)rawdata;
float* dst = blob.ptr<float>();
for (size_t i = 0, total = blob.total(); i < total; i++)
dst[i] = onnx_dtype::e8m0ToF32(src[i]);
}
else if (datatype == opencv_onnx::TensorProto_DataType_FLOAT4E2M1)
{
blob.create((int)sizes.size(), sizes.data(), CV_16FC1);
const uchar* src = (const uchar*)rawdata;
hfloat* dst = blob.ptr<hfloat>();
for (size_t i = 0, total = blob.total(); i < total; i++)
dst[i] = hfloat(onnx_dtype::fp4ToF32(onnx_dtype::unpackNibble(src, i)));
}
else if (datatype == opencv_onnx::TensorProto_DataType_INT4)
{
blob.create((int)sizes.size(), sizes.data(), CV_8SC1);
const uchar* src = (const uchar*)rawdata;
schar* dst = blob.ptr<schar>();
for (size_t i = 0, total = blob.total(); i < total; i++)
dst[i] = onnx_dtype::int4SignExtend(onnx_dtype::unpackNibble(src, i));
}
else if (datatype == opencv_onnx::TensorProto_DataType_UINT4)
{
blob.create((int)sizes.size(), sizes.data(), CV_8UC1);
const uchar* src = (const uchar*)rawdata;
uchar* dst = blob.ptr<uchar>();
for (size_t i = 0, total = blob.total(); i < total; i++)
dst[i] = onnx_dtype::unpackNibble(src, i);
}
else
{
// @TODO: refactor the error handling

View File

@@ -61,9 +61,6 @@ static T getScalarFromMat(Mat m)
}
static std::string dataType2str(int dt)
{
const char* str =
@@ -111,6 +108,7 @@ protected:
Ptr<Graph> parseGraph(opencv_onnx::GraphProto* graph_proto, bool mainGraph);
void parseNode(const opencv_onnx::NodeProto& node_proto);
bool parseValueInfo(const opencv_onnx::ValueInfoProto& valueInfoProto, ArgData& data);
int findGraphTensorOnnxType(const std::string& name) const;
Mat parseTensor(const opencv_onnx::TensorProto& tensorProto);
void rememberMissingOp(const std::string& opname);
@@ -718,6 +716,14 @@ Net ONNXImporter2::parseModel()
parseOperatorSet();
Ptr<Graph> mainGraph = parseGraph(graph_proto, true);
netimpl->mainGraph = mainGraph;
// Capture declared output dtypes before prepareForInference() replaces them with computed ones.
if (mainGraph)
{
const std::vector<Arg>& outs = mainGraph->outputs();
netimpl->mainGraphOutTypes.resize(outs.size());
for (size_t i = 0; i < outs.size(); i++)
netimpl->mainGraphOutTypes[i] = netimpl->args.at(outs[i].idx).type;
}
netimpl->modelFormat = DNN_MODEL_ONNX;
netimpl->originalLayout = DATA_LAYOUT_NCHW;
// netimpl->onnx_opset = onnx_opset;
@@ -1689,10 +1695,37 @@ void ONNXImporter2::parseCast2(LayerParams& layerParams, const opencv_onnx::Node
addLayer(layerParams, node_proto);
}
// Returns a graph tensor's ONNX data_type by name, or -1 if unknown.
int ONNXImporter2::findGraphTensorOnnxType(const std::string& name) const
{
if (!curr_graph_proto)
return -1;
const opencv_onnx::GraphProto& g = *curr_graph_proto;
for (int i = 0; i < g.input_size(); i++)
if (g.input(i).name() == name && g.input(i).has_type() && g.input(i).type().has_tensor_type())
return g.input(i).type().tensor_type().elem_type();
for (int i = 0; i < g.value_info_size(); i++)
if (g.value_info(i).name() == name && g.value_info(i).has_type() && g.value_info(i).type().has_tensor_type())
return g.value_info(i).type().tensor_type().elem_type();
for (int i = 0; i < g.output_size(); i++)
if (g.output(i).name() == name && g.output(i).has_type() && g.output(i).type().has_tensor_type())
return g.output(i).type().tensor_type().elem_type();
for (int i = 0; i < g.initializer_size(); i++)
if (g.initializer(i).name() == name)
return g.initializer(i).data_type();
return -1;
}
void ONNXImporter2::parseCastLike(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
CV_CheckEQ(node_proto.input_size(), 2, "CastLike requires two inputs");
layerParams.type = "Cast2";
if (!layerParams.has("to"))
{
int elemType = findGraphTensorOnnxType(node_proto.input(1));
if (elemType > 0)
layerParams.set("to", elemType);
}
addLayer(layerParams, node_proto);
}

View File

@@ -416,6 +416,174 @@ CASE(test_castlike_STRING_to_FLOAT_expanded)
#if SKIP_SET_1
SKIP;
#endif
CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FN)
SKIP;
CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ)
SKIP;
CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ_expanded)
SKIP;
CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FN_expanded)
SKIP;
CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2)
SKIP;
CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ)
SKIP;
CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ_expanded)
SKIP;
CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2_expanded)
SKIP;
CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FN)
SKIP;
CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ)
SKIP;
CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2)
SKIP;
CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ)
SKIP;
CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ_expanded)
SKIP;
CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2_expanded)
SKIP;
CASE(test_castlike_FLOAT_to_UINT4)
SKIP;
CASE(test_castlike_FLOAT_to_UINT4_expanded)
SKIP;
CASE(test_castlike_FLOAT_to_FLOAT4E2M1)
SKIP;
CASE(test_castlike_FLOAT_to_FLOAT4E2M1_expanded)
SKIP;
CASE(test_castlike_FLOAT_to_FLOAT8E4M3FN)
SKIP;
CASE(test_castlike_FLOAT_to_FLOAT8E4M3FNUZ)
SKIP;
CASE(test_castlike_FLOAT_to_FLOAT8E4M3FNUZ_expanded)
SKIP;
CASE(test_castlike_FLOAT_to_FLOAT8E4M3FN_expanded)
SKIP;
CASE(test_castlike_FLOAT_to_FLOAT8E5M2)
SKIP;
CASE(test_castlike_FLOAT_to_FLOAT8E5M2FNUZ)
SKIP;
CASE(test_castlike_FLOAT_to_FLOAT8E5M2FNUZ_expanded)
SKIP;
CASE(test_castlike_FLOAT_to_FLOAT8E5M2_expanded)
SKIP;
CASE(test_castlike_FLOAT_to_INT4)
SKIP;
CASE(test_castlike_FLOAT_to_INT4_expanded)
SKIP;
CASE(test_cast_FLOAT16_to_FLOAT4E2M1)
SKIP;
CASE(test_cast_FLOAT16_to_FLOAT8E4M3FN)
SKIP;
CASE(test_cast_FLOAT16_to_FLOAT8E4M3FNUZ)
SKIP;
CASE(test_cast_FLOAT16_to_FLOAT8E5M2)
SKIP;
CASE(test_cast_FLOAT16_to_FLOAT8E5M2FNUZ)
SKIP;
CASE(test_cast_FLOAT16_to_INT4)
SKIP;
CASE(test_cast_FLOAT16_to_UINT4)
SKIP;
CASE(test_cast_FLOAT4E2M1_to_FLOAT)
SKIP;
CASE(test_cast_FLOAT4E2M1_to_FLOAT16)
SKIP;
CASE(test_cast_FLOAT8E4M3FNUZ_to_FLOAT)
SKIP;
CASE(test_cast_FLOAT8E4M3FNUZ_to_FLOAT16)
SKIP;
CASE(test_cast_FLOAT8E4M3FN_to_FLOAT)
SKIP;
CASE(test_cast_FLOAT8E4M3FN_to_FLOAT16)
SKIP;
CASE(test_cast_FLOAT8E5M2FNUZ_to_FLOAT)
SKIP;
CASE(test_cast_FLOAT8E5M2FNUZ_to_FLOAT16)
SKIP;
CASE(test_cast_FLOAT8E5M2_to_FLOAT)
SKIP;
CASE(test_cast_FLOAT8E5M2_to_FLOAT16)
SKIP;
CASE(test_cast_FLOAT_to_FLOAT4E2M1)
SKIP;
CASE(test_cast_FLOAT_to_FLOAT8E4M3FN)
SKIP;
CASE(test_cast_FLOAT_to_FLOAT8E4M3FNUZ)
SKIP;
CASE(test_cast_FLOAT_to_FLOAT8E5M2)
SKIP;
CASE(test_cast_FLOAT_to_FLOAT8E5M2FNUZ)
SKIP;
CASE(test_cast_FLOAT_to_INT4)
SKIP;
CASE(test_cast_FLOAT_to_UINT4)
SKIP;
CASE(test_cast_INT4_to_FLOAT)
SKIP;
CASE(test_cast_INT4_to_FLOAT16)
SKIP;
CASE(test_cast_INT4_to_INT8)
SKIP;
CASE(test_cast_UINT4_to_FLOAT)
SKIP;
CASE(test_cast_UINT4_to_FLOAT16)
SKIP;
CASE(test_cast_UINT4_to_UINT8)
SKIP;
CASE(test_cast_e8m0_FLOAT16_to_FLOAT8E8M0)
SKIP;
CASE(test_cast_e8m0_FLOAT8E8M0_to_FLOAT)
SKIP;
CASE(test_cast_e8m0_FLOAT8E8M0_to_FLOAT16)
SKIP;
CASE(test_cast_e8m0_FLOAT_to_FLOAT8E8M0)
SKIP;
CASE(test_cast_no_saturate_FLOAT16_to_FLOAT8E4M3FN)
SKIP;
CASE(test_cast_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ)
SKIP;
CASE(test_cast_no_saturate_FLOAT16_to_FLOAT8E5M2)
SKIP;
CASE(test_cast_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ)
SKIP;
CASE(test_cast_no_saturate_FLOAT_to_FLOAT8E4M3FN)
SKIP;
CASE(test_cast_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ)
SKIP;
CASE(test_cast_no_saturate_FLOAT_to_FLOAT8E5M2)
SKIP;
CASE(test_cast_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ)
SKIP;
CASE(test_castlike_FLOAT16_to_FLOAT4E2M1)
SKIP;
CASE(test_castlike_FLOAT16_to_FLOAT4E2M1_expanded)
SKIP;
CASE(test_castlike_FLOAT16_to_FLOAT8E4M3FN)
SKIP;
CASE(test_castlike_FLOAT16_to_FLOAT8E4M3FNUZ)
SKIP;
CASE(test_castlike_FLOAT16_to_FLOAT8E4M3FNUZ_expanded)
SKIP;
CASE(test_castlike_FLOAT16_to_FLOAT8E4M3FN_expanded)
SKIP;
CASE(test_castlike_FLOAT16_to_FLOAT8E5M2)
SKIP;
CASE(test_castlike_FLOAT16_to_FLOAT8E5M2FNUZ)
SKIP;
CASE(test_castlike_FLOAT16_to_FLOAT8E5M2FNUZ_expanded)
SKIP;
CASE(test_castlike_FLOAT16_to_FLOAT8E5M2_expanded)
SKIP;
CASE(test_castlike_FLOAT16_to_INT4)
SKIP;
CASE(test_castlike_FLOAT16_to_INT4_expanded)
SKIP;
CASE(test_castlike_FLOAT16_to_UINT4)
SKIP;
CASE(test_castlike_FLOAT16_to_UINT4_expanded)
SKIP;
CASE(test_ceil)
// no filter
CASE(test_ceil_example)

View File

@@ -39,63 +39,7 @@
"test_bernoulli_expanded", // ---- same as above ---
"test_bernoulli_seed", // ---- same as above ---
"test_bernoulli_seed_expanded", // ---- same as above ---
"test_cast_FLOAT16_to_FLOAT4E2M1",
"test_cast_FLOAT16_to_FLOAT8E4M3FN",
"test_cast_FLOAT16_to_FLOAT8E4M3FNUZ",
"test_cast_FLOAT16_to_FLOAT8E5M2",
"test_cast_FLOAT16_to_FLOAT8E5M2FNUZ",
"test_cast_FLOAT16_to_INT4",
"test_cast_FLOAT16_to_UINT4",
"test_cast_FLOAT4E2M1_to_FLOAT",
"test_cast_FLOAT4E2M1_to_FLOAT16",
"test_cast_FLOAT8E4M3FNUZ_to_FLOAT",
"test_cast_FLOAT8E4M3FNUZ_to_FLOAT16",
"test_cast_FLOAT8E4M3FN_to_FLOAT",
"test_cast_FLOAT8E4M3FN_to_FLOAT16",
"test_cast_FLOAT8E5M2FNUZ_to_FLOAT",
"test_cast_FLOAT8E5M2FNUZ_to_FLOAT16",
"test_cast_FLOAT8E5M2_to_FLOAT",
"test_cast_FLOAT8E5M2_to_FLOAT16",
"test_cast_FLOAT_to_FLOAT4E2M1",
"test_cast_FLOAT_to_FLOAT8E4M3FN",
"test_cast_FLOAT_to_FLOAT8E4M3FNUZ",
"test_cast_FLOAT_to_FLOAT8E5M2",
"test_cast_FLOAT_to_FLOAT8E5M2FNUZ",
"test_cast_FLOAT_to_INT4",
"test_cast_FLOAT_to_UINT4",
"test_cast_INT4_to_FLOAT",
"test_cast_INT4_to_FLOAT16",
"test_cast_INT4_to_INT8",
"test_cast_UINT4_to_FLOAT",
"test_cast_UINT4_to_FLOAT16",
"test_cast_UINT4_to_UINT8",
"test_cast_e8m0_FLOAT16_to_FLOAT8E8M0",
"test_cast_e8m0_FLOAT8E8M0_to_FLOAT",
"test_cast_e8m0_FLOAT8E8M0_to_FLOAT16",
"test_cast_e8m0_FLOAT_to_FLOAT8E8M0",
"test_cast_no_saturate_FLOAT16_to_FLOAT8E4M3FN",
"test_cast_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ",
"test_cast_no_saturate_FLOAT16_to_FLOAT8E5M2",
"test_cast_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ",
"test_cast_no_saturate_FLOAT_to_FLOAT8E4M3FN",
"test_cast_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ",
"test_cast_no_saturate_FLOAT_to_FLOAT8E5M2",
"test_cast_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ",
"test_castlike_FLOAT16_to_FLOAT4E2M1",
"test_castlike_FLOAT16_to_FLOAT4E2M1_expanded",
"test_castlike_FLOAT16_to_FLOAT8E4M3FN",
"test_castlike_FLOAT16_to_FLOAT8E4M3FNUZ",
"test_castlike_FLOAT16_to_FLOAT8E4M3FNUZ_expanded",
"test_castlike_FLOAT16_to_FLOAT8E4M3FN_expanded",
"test_castlike_FLOAT16_to_FLOAT8E5M2",
"test_castlike_FLOAT16_to_FLOAT8E5M2FNUZ",
"test_castlike_FLOAT16_to_FLOAT8E5M2FNUZ_expanded",
"test_castlike_FLOAT16_to_FLOAT8E5M2_expanded",
"test_castlike_FLOAT16_to_INT4",
"test_castlike_FLOAT16_to_INT4_expanded",
"test_castlike_FLOAT16_to_UINT4",
"test_castlike_FLOAT16_to_UINT4_expanded",
"test_castlike_FLOAT4E2M1_to_FLOAT",
"test_castlike_FLOAT4E2M1_to_FLOAT", // opencv_extra stores empty input data for these from-exotic CastLike cases
"test_castlike_FLOAT4E2M1_to_FLOAT16",
"test_castlike_FLOAT4E2M1_to_FLOAT16_expanded",
"test_castlike_FLOAT4E2M1_to_FLOAT_expanded",
@@ -115,21 +59,7 @@
"test_castlike_FLOAT8E5M2_to_FLOAT16",
"test_castlike_FLOAT8E5M2_to_FLOAT16_expanded",
"test_castlike_FLOAT8E5M2_to_FLOAT_expanded",
"test_castlike_FLOAT_to_FLOAT4E2M1",
"test_castlike_FLOAT_to_FLOAT4E2M1_expanded",
"test_castlike_FLOAT_to_FLOAT8E4M3FN",
"test_castlike_FLOAT_to_FLOAT8E4M3FNUZ",
"test_castlike_FLOAT_to_FLOAT8E4M3FNUZ_expanded",
"test_castlike_FLOAT_to_FLOAT8E4M3FN_expanded",
"test_castlike_FLOAT_to_FLOAT8E5M2",
"test_castlike_FLOAT_to_FLOAT8E5M2FNUZ",
"test_castlike_FLOAT_to_FLOAT8E5M2FNUZ_expanded",
"test_castlike_FLOAT_to_FLOAT8E5M2_expanded",
"test_castlike_FLOAT_to_INT4",
"test_castlike_FLOAT_to_INT4_expanded",
"test_castlike_FLOAT_to_STRING",
"test_castlike_FLOAT_to_UINT4",
"test_castlike_FLOAT_to_UINT4_expanded",
"test_castlike_INT4_to_FLOAT",
"test_castlike_INT4_to_FLOAT16",
"test_castlike_INT4_to_FLOAT16_expanded",
@@ -143,20 +73,6 @@
"test_castlike_UINT4_to_FLOAT_expanded",
"test_castlike_UINT4_to_UINT8",
"test_castlike_UINT4_to_UINT8_expanded",
"test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FN",
"test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ",
"test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ_expanded",
"test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FN_expanded",
"test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2",
"test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ",
"test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ_expanded",
"test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2_expanded",
"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FN",
"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ",
"test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2",
"test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ",
"test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ_expanded",
"test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2_expanded",
"test_clip_min_greater_than_max",
"test_col2im",
"test_col2im_5d",

View File

@@ -315,8 +315,8 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info)
template<>
PyObject* pyopencv_from(const cv::Mat& m)
{
// NumPy has no bfloat16 dtype: widen CV_16BF to float32 (lossless).
if( m.depth() == CV_16BF )
// NumPy has no bfloat16 or float8 dtype: widen these to float32 (lossless).
if( m.depth() == CV_16BF || m.depth() == CV_8F_E4M3FN || m.depth() == CV_8F_E4M3FNUZ )
{
cv::Mat m32f;
ERRWRAP2(m.convertTo(m32f, CV_32F));

View File

@@ -1460,7 +1460,7 @@ norm_flt_(const _Tp* src1, const _Tp* src2, size_t total, int cn, int normType,
double norm(InputArray _src, int normType, InputArray _mask)
{
Mat src = _src.getMat(), mask = _mask.getMat();
if( src.depth() == CV_16F )
if( src.depth() == CV_16F || src.depth() == CV_8F_E4M3FN || src.depth() == CV_8F_E4M3FNUZ )
{
Mat src32f;
src.convertTo(src32f, CV_32F);
@@ -1660,6 +1660,12 @@ double norm(InputArray _src1, InputArray _src2, int normType, InputArray _mask)
case CV_16BF:
result = norm_flt_<cv::bfloat, short>((const cv::bfloat*)sptr1, (const cv::bfloat*)sptr2, total, cn, normType, result, mptr);
break;
case CV_8F_E4M3FN:
result = norm_flt_<cv::fp8_t, schar>((const cv::fp8_t*)sptr1, (const cv::fp8_t*)sptr2, total, cn, normType, result, mptr);
break;
case CV_8F_E4M3FNUZ:
result = norm_flt_<cv::fp8a_t, schar>((const cv::fp8a_t*)sptr1, (const cv::fp8a_t*)sptr2, total, cn, normType, result, mptr);
break;
default:
CV_Error(Error::StsUnsupportedFormat, "");
};