eLynx SDK
v3.3.0 C++ image processing API reference |
To minimize code and have generic algorithms.
To be concrete lets implement Min function without using macro:
ubyte Min(ubyte iA, ubyte iB) { return (iA<iB) ? iA : iB; } ushort Min(ushort iA, ushort iB) { return (iA<iB) ? iA : iB; } int Min(int iA, int iB) { return (iA<iB) ? iA : iB; } float Min(float iA, float iB) { return (iA<iB) ? iA : iB; } double Min(double iA, double iB) { return (iA<iB) ? iA : iB; }
Now using template we can factorize code writing:
template<typename T> T Min(T iA, T iB) { return (iA<iB) ? iA : iB; }
In sdk we define at low level in CoreTypes.h:
An enum eLynx::EResolution has been created.
We need:
To have common operators for all of these types we define template eLynx::ResolutionTypeTraits:
template<typename T> struct ResolutionTypeTraits { static const EResolution _Resolution; // The resolution implementation. static const uint _Bits; // Number of bits used in resolution static const bool _Signed; // Does resolution is signed or not static const bool _Floated; // Does resolution support floating point static const T _Min; // Min value in resolution range static const T _Max; // Max value in resolution range static const double _MinInDouble; // Min value in resolution range cast in double resolution static const double _MaxInDouble; // Max value in resolution range cast in double resolution static const double _NormScale; // Scaling coefficient to have normalized double };
Lets see what's going with simple math first. We implement mean of two values function:
template <typename T> T elxTestMean1(T iV1, T iV2) { const T sum = iV1 + iV2; return (sum / T(2)); } ubyte a=100, b=200, c=elxTestMean1(a,b); ::printf("Mean(%d, %d) = %d with ubyte\n", a,b,c); ushort a=100, b=200, c=elxTestMean1(a,b); ::printf("Mean(%d, %d) = %d with ushort\n", a,b,c); int a=100, b=200, c=elxTestMean1(a,b); ::printf("Mean(%d, %d) = %d with int\n", a,b,c); float a=100, b=200, c=elxTestMean1(a,b); ::printf("Mean(%f, %f) = %f with float\n", a,b,c); double a=100, b=200, c=elxTestMean1(a,b); ::printf("Mean(%f, %f) = %f with double\n", a,b,c);
Results are:
With ubyte we expected to have 150 but we get 22. We are facing to overflow error.
ubyte range is [0,255]. 100+200 = 300, but 300 can't be stored with only 8 bits.
300 = 1 0010 1100 in binary it need 9 bits. If we restrict to 8 bits loosing the most significant bit we get 0010 1100 = 44. So with ubyte 100+200=44 and 44/2=22.
We must compute whole operation with more bits and clamp result to original resolution.
We are leading to the same problem with ushort, int and float type.
Lets define a typedef named SumOverflow_type for ResolutionTypeTraits<T> implementations.
We also define _MinS, _MaxS and ClampS:
static const SumOverflow_type _MinS; // min cast into SumOverflow_type static const SumOverflow_type _MaxS; // max cast into SumOverflow_type static ubyte ClampS(SumOverflow_type iValue) { if (iValue < _MinS) return _Min; if (iValue > _MaxS) return _Max; return ubyte(iValue); }
Now we can write:
template <typename T> T elxTestMean2(T iV1, T iV2) { typedef typename ResolutionTypeTraits<T>::SumOverflow_type S_type; S_type sum = iV1 + iV2; sum /= 2; return ResolutionTypeTraits<T>::ClampS(sum); }
And replacing elxTestMean1 by elxTestMean2 the results are:
We can now write simple additions without overflow problem using template resolution. Try now writing a function with more additions. Lets use multiplications:
template <typename T> T elxFoo1(T iV) { typedef typename ResolutionTypeTraits<T>::SumOverflow_type S_type; S_type f = S_type(iV); S_type n = (f+1)*(f+2)*(f+3); S_type d = f*f; f = n / d; return ResolutionTypeTraits<T>::ClampS(f); } ubyte a=255; ::printf("Foo1(%d) = %d with ubyte\n", a, elxFoo1(a)); ushort a=255; ::printf("Foo1(%d) = %d with ushort\n", a, elxFoo1(a)); int a=255; ::printf("Foo1(%d) = %d with int\n", a, elxFoo1(a)); float a=255; ::printf("Foo1(%f) = %f with float\n", a, elxFoo1(a)); double a=255; ::printf("Foo1(%f) = %f with double\n", a, elxFoo1(a));
Results are:
We get overflow problem because ResolutionTypeTraits<T>::SumOverflow_type has no enough bits for temporary computation. So we must work in higher bit definition. Lets say MulOverflow_type:
template <typename T> T elxFoo2(T iV) { typedef typename ResolutionTypeTraits<T>::MulOverflow_type M_type; M_type f = M_type(iV); M_type n = (f+1)*(f+2)*(f+3); M_type d = f*f; f = n / d; return ResolutionTypeTraits<T>::ClampM(f); }
using elxFoo2 instead of elxFoo1 results are:
With ubyte we wont get 261 but 255 which is the max we can store with 8 bits, result is 'valid'.