• Vadim Levin's avatar
    Merge pull request #15915 from VadimLevin:dev/norm_fix · 31289d2f
    Vadim Levin authored
    Fix implicit conversion from array to scalar in python bindings
    
    * Fix wrong conversion behavior for primitive types
    
      - Introduce ArgTypeInfo namedtuple instead of plain tuple.
        If strict conversion parameter for type is set to true, it is
        handled like object argument in PyArg_ParseTupleAndKeywords and
        converted to concrete type with the appropriate pyopencv_to function
        call.
      - Remove deadcode and unused variables.
      - Fix implicit conversion from numpy array with 1 element to scalar
      - Fix narrowing conversion to size_t type.
    
    * Fix wrong conversion behavior for primitive types
    
      - Introduce ArgTypeInfo namedtuple instead of plain tuple.
        If strict conversion parameter for type is set to true, it is
        handled like object argument in PyArg_ParseTupleAndKeywords and
        converted to concrete type with the appropriate pyopencv_to function
        call.
      - Remove deadcode and unused variables.
      - Fix implicit conversion from numpy array with 1 element to scalar
      - Fix narrowing conversion to size_t type.·
      - Enable tests with wrong conversion behavior
      - Restrict passing None as value
      - Restrict bool to integer/floating types conversion
    
    * Add PyIntType support for Python 2
    
    * Remove possible narrowing conversion of size_t
    
    * Bindings conversion update
    
      - Remove unused macro
      - Add better conversion for types to numpy types descriptors
      - Add argument name to fail messages
      - NoneType treated as a valid argument. Better handling will be added
        as a standalone patch
    
    * Add descriptor specialization for size_t
    
    * Add check for signed to unsigned integer conversion safety
    
      - If signed integer is positive it can be safely converted
        to unsigned
      - Add check for plain python 2 objects
      - Add check for numpy scalars
      - Add simple type_traits implementation for better code style
    
    * Resolve type "overflow" false negative in safe casting check
    
     - Move type_traits to separate header
    
    * Add copyright message to type_traits.hpp
    
    * Limit conversion scope for integral numpy types
    
      - Made canBeSafelyCasted specialized only for size_t, so
        type_traits header became unused and was removed.
      - Added clarification about descriptor pointer
    31289d2f
test_norm.py 5.63 KB
#!/usr/bin/env python

from itertools import product
from functools import reduce

import numpy as np
import cv2 as cv

from tests_common import NewOpenCVTests


def norm_inf(x, y=None):
    def norm(vec):
        return np.linalg.norm(vec.flatten(), np.inf)

    x = x.astype(np.float64)
    return norm(x) if y is None else norm(x - y.astype(np.float64))


def norm_l1(x, y=None):
    def norm(vec):
        return np.linalg.norm(vec.flatten(), 1)

    x = x.astype(np.float64)
    return norm(x) if y is None else norm(x - y.astype(np.float64))


def norm_l2(x, y=None):
    def norm(vec):
        return np.linalg.norm(vec.flatten())

    x = x.astype(np.float64)
    return norm(x) if y is None else norm(x - y.astype(np.float64))


def norm_l2sqr(x, y=None):
    def norm(vec):
        return np.square(vec).sum()

    x = x.astype(np.float64)
    return norm(x) if y is None else norm(x - y.astype(np.float64))


def norm_hamming(x, y=None):
    def norm(vec):
        return sum(bin(i).count('1') for i in vec.flatten())

    return norm(x) if y is None else norm(np.bitwise_xor(x, y))


def norm_hamming2(x, y=None):
    def norm(vec):
        def element_norm(element):
            binary_str = bin(element).split('b')[-1]
            if len(binary_str) % 2 == 1:
                binary_str = '0' + binary_str
            gen = filter(lambda p: p != '00',
                         (binary_str[i:i+2]
                          for i in range(0, len(binary_str), 2)))
            return sum(1 for _ in gen)

        return sum(element_norm(element) for element in vec.flatten())

    return norm(x) if y is None else norm(np.bitwise_xor(x, y))


norm_type_under_test = {
    cv.NORM_INF: norm_inf,
    cv.NORM_L1: norm_l1,
    cv.NORM_L2: norm_l2,
    cv.NORM_L2SQR: norm_l2sqr,
    cv.NORM_HAMMING: norm_hamming,
    cv.NORM_HAMMING2: norm_hamming2
}

norm_name = {
    cv.NORM_INF: 'inf',
    cv.NORM_L1: 'L1',
    cv.NORM_L2: 'L2',
    cv.NORM_L2SQR: 'L2SQR',
    cv.NORM_HAMMING: 'Hamming',
    cv.NORM_HAMMING2: 'Hamming2'
}


def get_element_types(norm_type):
    if norm_type in (cv.NORM_HAMMING, cv.NORM_HAMMING2):
        return (np.uint8,)
    else:
        return (np.uint8, np.int8, np.uint16, np.int16, np.int32, np.float32,
                np.float64)


def generate_vector(shape, dtype):
    if np.issubdtype(dtype, np.integer):
        return np.random.randint(0, 100, shape).astype(dtype)
    else:
        return np.random.normal(10., 12.5, shape).astype(dtype)


shapes = (1, 2, 3, 5, 7, 16, (1, 1), (2, 2), (3, 5), (1, 7))


class norm_test(NewOpenCVTests):

    def test_norm_for_one_array(self):
        np.random.seed(123)
        for norm_type, norm in norm_type_under_test.items():
            element_types = get_element_types(norm_type)
            for shape, element_type in product(shapes, element_types):
                array = generate_vector(shape, element_type)
                expected = norm(array)
                actual = cv.norm(array, norm_type)
                self.assertAlmostEqual(
                    expected, actual, places=2,
                    msg='Array {0} of {1} and norm {2}'.format(
                        array, element_type.__name__, norm_name[norm_type]
                    )
                )

    def test_norm_for_two_arrays(self):
        np.random.seed(456)
        for norm_type, norm in norm_type_under_test.items():
            element_types = get_element_types(norm_type)
            for shape, element_type in product(shapes, element_types):
                first = generate_vector(shape, element_type)
                second = generate_vector(shape, element_type)
                expected = norm(first, second)
                actual = cv.norm(first, second, norm_type)
                self.assertAlmostEqual(
                    expected, actual, places=2,
                    msg='Arrays {0} {1} of type {2} and norm {3}'.format(
                        first, second, element_type.__name__,
                        norm_name[norm_type]
                    )
                )

    def test_norm_fails_for_wrong_type(self):
        for norm_type in (cv.NORM_HAMMING, cv.NORM_HAMMING2):
            with self.assertRaises(Exception,
                                   msg='Type is not checked {0}'.format(
                                       norm_name[norm_type]
                                   )):
                cv.norm(np.array([1, 2], dtype=np.int32), norm_type)

    def test_norm_fails_for_array_and_scalar(self):
        for norm_type in norm_type_under_test:
            with self.assertRaises(Exception,
                                   msg='Exception is not thrown for {0}'.format(
                                       norm_name[norm_type]
                                   )):
                cv.norm(np.array([1, 2], dtype=np.uint8), 123, norm_type)

    def test_norm_fails_for_scalar_and_array(self):
        for norm_type in norm_type_under_test:
            with self.assertRaises(Exception,
                                   msg='Exception is not thrown for {0}'.format(
                                       norm_name[norm_type]
                                   )):
                cv.norm(4, np.array([1, 2], dtype=np.uint8), norm_type)

    def test_norm_fails_for_array_and_norm_type_as_scalar(self):
        for norm_type in norm_type_under_test:
            with self.assertRaises(Exception,
                                   msg='Exception is not thrown for {0}'.format(
                                       norm_name[norm_type]
                                   )):
                cv.norm(np.array([3, 4, 5], dtype=np.uint8),
                        norm_type, normType=norm_type)


if __name__ == '__main__':
    NewOpenCVTests.bootstrap()