Source code for intelligent_tracker.array_utils

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# (C) 2017 David Toro <davsamirtor@gmail.com>

# compatibility with python 2 and 3
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import
from builtins import object

# import build-in modules
import sys

# import third party modules
import cv2
import numpy as np
from numbers import Number
#from RRtoolbox.lib.arrayops import overlay

# special variables
# __all__ = []
__author__ = "David Toro"
# __copyright__ = "Copyright 2017, The <name> Project"
# __credits__ = [""]
__license__ = "GPL"
# __version__ = "1.0.0"
__maintainer__ = "David Toro"
__email__ = "davsamirtor@gmail.com"
# __status__ = "Pre-release"


[docs]def is_numpy(obj): """ returns True if object is obj numpy object """ return type(obj).__module__ == np.__name__
[docs]def convert(points, roll=None, _type=np.int, shift=None, apply="contours"): """ Convert contour or points. :param points: contours or points :param roll: roll points :param _type: convert points to type :param shift: shift points to (x, y) :param apply: apply operations the combination "contours-points-list-array" :return: converted points """ # normalize them to array conv = np.array(points, _type) assert not len(conv) or 2 == conv[0].size # roll array if roll is not None: conv = np.roll(conv, roll * 2) # shift array if shift is not None: conv += shift if apply is None: # determine operations by inspecting data apply = "" if not is_numpy(points): apply += "list" if len(points) and is_numpy(points[0]): apply += "-contours" else: apply += "-points" if "point" in apply: conv = conv.reshape(-1, 2) if "list" in apply: return [tuple(i) for i in conv] elif "contour" in apply: conv = conv.reshape(-1, 1, 2) if "list" in apply: return list(conv) #elif "array" in apply: # pass return conv
[docs]def norm_range(vec, lower=0, upper=255, type=int): """ clips vector to range [lower, upper] and a tuple with integers :param vec: vector :param lower: 0 :param upper: 255 :param type: int :return: """ newvec = [] upper = type(upper) lower = type(lower) for i in vec: if i > upper: newvec.append(upper) elif i < lower: newvec.append(lower) else: newvec.append(type(i)) return tuple(newvec)
[docs]def draw_contour_groups(contour_groups, shape=None, binary=False): """ draw contours in separate colors :param contour_groups: list of contours :param shape: shape to draw on. If None shape is calculated. :param binary: True to draw with Ones, False to draw with colors. :return: image """ if shape is None: # get the maximum coordinates in x, y max_ = np.max([np.max([np.max(contours,0) for contours in group],0) for group in contour_groups],0).reshape(2) # calculate pad to correct small contours pad = 1 #max_*np.exp(-max_*0.1) + 1 # get fitting window in all contours x, y = max_ + pad # get fitting shape shape = int(y), int(x) if binary: img = np.zeros(shape[:2]) else: img = np.zeros(shape[:2]+(3,)) vis = img.copy() for contours in contour_groups: if binary: color = 1 else: color = norm_range(np.random.rand(3) * 255, lower=20) over = cv2.fillPoly(img.copy(), contours, color, cv2.LINE_4) #over = cv2.drawContours(img.copy(), contours, -1, color, 0) if binary: vis += over else: vis = overlay(vis, over, 0.3) #vis = cv2.addWeighted(vis, 0.5, over, 0.5, 0.0) return vis
[docs]def find_roll_inv(ans, expected): """ roll an array until it is best matched to an expected array :param ans: array to roll :param expected: array which ans must be rolled to :return: best result, inversion, roll, alltrue """ assert ans.shape == expected.shape assert ans.size == expected.size best = 0 best_ans = None inv = False roll = 0 for inv in (False, True): if inv: ans = ans[::-1] # copy it for roll in range(ans.size): comp = np.roll(ans, roll) == expected ind = np.sum(comp) # equivalent to np.alltrue(comp) if comp.min() == True: return comp, inv, roll, True if ind > best: best_ans = comp best = ind return best_ans, inv, roll, False
[docs]def check_contours(ans, expected, ignore_shape=False): """ check to contours are the same in an strict or lazy way. :param ans: contours to check :param expected: expected contours or ground truth :param ignore_shape: True to ignore order, shapes and check by approximation if ans yields similar results as expected. if ignore_shape is a number then it will be the threshold which is 0.1 when ignore_shape = True. :return: """ if ignore_shape: if not isinstance(ignore_shape, Number): ignore_shape = 0.1 # check that cnts are equal by comparing their moments if len(ans) == 0 and len(expected) == 0: # both are empty return True elif len(ans) == 0 or len(expected) == 0: # one of them is empty return False temp = draw_contour_groups([ans, expected], binary=True) body = (temp != 0).sum() + 0.0000001 not_overlaped = (temp == 1).sum() ret = not_overlaped / body if ret > ignore_shape: return False # potentially risky if cnts are un ordered #for ans_c, exp_c in zip(ans, expected): # ret = cv2.matchShapes(ans_c, exp_c, 1, 0.0) # if ret > 0.01: # return False else: if len(ans) != len(expected): return False for ans_c, exp_c in zip(ans, expected): # normalize them to points ans_c = np.array([i for i in convert(ans_c).reshape(-1, 2)]) exp_c = np.array([i for i in convert(exp_c).reshape(-1, 2)]) # if after normalization they are not the same shape # then they should not be equal if ans_c.shape != exp_c.shape: return False if not find_roll_inv(ans_c, exp_c)[3]: return False return True