Skip to content

Instantly share code, notes, and snippets.

@fumin
Created April 10, 2022 13:58
Show Gist options
  • Save fumin/0c12043910465c50cd1fbd0750d82ed0 to your computer and use it in GitHub Desktop.
Save fumin/0c12043910465c50cd1fbd0750d82ed0 to your computer and use it in GitHub Desktop.
pretty printer for opencv Mat
numpy_code = """
# -*- coding: utf-8 -*-
# Copyright (c) 2014, Almar Klein and Wade Brainerd
# tinynumpy is distributed under the terms of the MIT License.
#
# Original code by Wade Brainerd (https://github.com/wadetb/tinyndarray)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ""
# A lightweight, pure Python, numpy compliant ndarray class.
# The documenation in this module is rather compact. For details on each
# function, see the corresponding documentation at:
# http://docs.scipy.org/doc/numpy/reference/index.html Be aware that the
# behavior of tinynumpy may deviate in some ways from numpy, or that
# certain features may not be supported.
# ""
# todo: keep track of readonly better
# todo: mathematical operators
# todo: more methods?
# todo: logspace, meshgrid
# todo: Fortran order?
from __future__ import division
from __future__ import absolute_import
import sys
import ctypes
from math import sqrt
from copy import copy, deepcopy
from collections import Iterable
import operator
#import tinynumpy.tinylinalg as linalg
#from tinynumpy.tinylinalg import LinAlgError as LinAlgError
# Python 2/3 compat
if sys.version_info >= (3, ):
xrange = range
# Define version numer
__version__ = '0.0.1dev'
# Define dtypes: struct name, short name, numpy name, ctypes type
_dtypes = [('B', 'b1', 'bool', ctypes.c_bool),
('b', 'i1', 'int8', ctypes.c_int8),
('B', 'u1', 'uint8', ctypes.c_uint8),
('h', 'i2', 'int16', ctypes.c_int16),
('H', 'u2', 'uint16', ctypes.c_uint16),
('i', 'i4', 'int32', ctypes.c_int32),
('I', 'u4', 'uint32', ctypes.c_uint32),
('q', 'i8', 'int64', ctypes.c_int64),
('Q', 'u8', 'uint64', ctypes.c_uint64),
('f', 'f4', 'float32', ctypes.c_float),
('d', 'f8', 'float64', ctypes.c_double),
]
# Inject common dtype names
_known_dtypes = [d[2] for d in _dtypes]
for d in _known_dtypes:
globals()[d] = d
newaxis = None
nan = float('nan')
def _convert_dtype(dtype, to='numpy'):
# "" Convert dtype, if could not find, pass as it was.
# ""
if dtype is None:
return dtype
dtype = str(dtype)
index = {'array':0, 'short':1, 'numpy':2, 'ctypes':3}[to]
for dd in _dtypes:
if dtype in dd:
return dd[index]
return dtype # Otherwise return original
def _ceildiv(a, b):
return -(-a // b)
def _get_step(view):
# "" Return step to walk over array. If 1, the array is fully
# C-contiguous. If 0, the striding is such that one cannot
# step through the array.
# ""
cont_strides = _strides_for_shape(view.shape, view.itemsize)
step = view.strides[-1] // cont_strides[-1]
corrected_strides = tuple([i * step for i in cont_strides])
almost_cont = view.strides == corrected_strides
if almost_cont:
return step
else:
return 0 # not contiguous
def _strides_for_shape(shape, itemsize):
strides = []
stride_product = 1
for s in reversed(shape):
strides.append(stride_product)
stride_product *= s
return tuple([i * itemsize for i in reversed(strides)])
def _size_for_shape(shape):
stride_product = 1
for s in shape:
stride_product *= s
return stride_product
def squeeze_strides(s):
# "" Pop strides for singular dimensions. ""
return tuple([s[0]] + [s[i] for i in range(1, len(s)) if s[i] != s[i-1]])
def _shape_from_object(obj):
shape = []
# todo: make more efficient, use len() etc
def _shape_from_object_r(index, element, axis):
try:
for i, e in enumerate(element):
_shape_from_object_r(i, e, axis+1)
while len(shape) <= axis:
shape.append(0)
l = i + 1
s = shape[axis]
if l > s:
shape[axis] = l
except TypeError:
pass
_shape_from_object_r(0, obj, 0)
return tuple(shape)
def _assign_from_object(array, obj):
key = []
# todo: make more efficient, especially the try-except
def _assign_from_object_r(element):
try:
for i, e in enumerate(element):
key.append(i)
_assign_from_object_r(e)
key.pop()
except TypeError:
array[tuple(key)] = element
_assign_from_object_r(obj)
def _increment_mutable_key(key, shape):
for axis in reversed(xrange(len(shape))):
key[axis] += 1
if key[axis] < shape[axis]:
return True
if axis == 0:
return False
key[axis] = 0
def _key_for_index(index, shape):
key = []
cumshape = [1]
for i in reversed(shape):
cumshape.insert(0, cumshape[0] * i)
for s in cumshape[1:-1]:
n = index // s
key.append(n)
index -= n * s
key.append(index)
return tuple(key)
def _zerositer(n):
for i in xrange(n):
yield 0
## Public functions
def array(obj, dtype=None, copy=True, order=None):
# "" array(obj, dtype=None, copy=True, order=None)
#Create a new array. If obj is an ndarray, and copy=False, a view
#of that array is returned. For details see:
#http://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html
# ""
dtype = _convert_dtype(dtype)
if isinstance(obj, ndarray):
# From existing array
a = obj.view()
if dtype is not None and dtype != a.dtype:
a = a.astype(dtype)
elif copy:
a = a.copy()
return a
if hasattr(obj, '__array_interface__'):
# From something that looks like an array, we can create
# the ctypes array for this and use that as a buffer
D = obj.__array_interface__
# Get dtype
dtype_orig = _convert_dtype(D['typestr'][1:])
# Create array
if D['strides']:
itemsize = int(D['typestr'][-1])
bufsize = D['strides'][0] * D['shape'][0] // itemsize
else:
bufsize = _size_for_shape(D['shape'])
BufType = (_convert_dtype(dtype_orig, 'ctypes') * bufsize)
buffer = BufType.from_address(D['data'][0])
a = ndarray(D['shape'], dtype_orig,
buffer=buffer, strides=D['strides'], order=order)
# Convert or copy?
if dtype is not None and dtype != dtype_orig:
a = a.astype(dtype)
elif copy:
a = a.copy()
return a
else:
# From some kind of iterable
shape = _shape_from_object(obj)
# Try to derive dtype
if dtype is None:
el = obj
while isinstance(el, (tuple, list)) and el:
el = el[0]
if isinstance(el, int):
dtype = 'int64'
# Create array
a = ndarray(shape, dtype, order=None)
_assign_from_object(a, obj)
return a
def zeros_like(a, dtype=None, order=None):
#"" Return an array of zeros with the same shape and type as a given array.
#""
dtype = a.dtype if dtype is None else dtype
return zeros(a.shape, dtype, order)
def ones_like(a, dtype=None, order=None):
#"" Return an array of ones with the same shape and type as a given array.
#""
dtype = a.dtype if dtype is None else dtype
return ones(a.shape, dtype, order)
def empty_like(a, dtype=None, order=None):
#"" Return a new array with the same shape and type as a given array.
#""
dtype = a.dtype if dtype is None else dtype
return empty(a.shape, dtype, order)
def zeros(shape, dtype=None, order=None):
#""Return a new array of given shape and type, filled with zeros
#""
return empty(shape, dtype, order)
def ones(shape, dtype=None, order=None):
#""Return a new array of given shape and type, filled with ones
#""
a = empty(shape, dtype, order)
a.fill(1)
return a
def eye(size):
#""Return a new 2d array with given dimensions, filled with ones on the
#diagonal and zeros elsewhere.
#""
a = zeros((size,size))
for i in xrange(size):
a[i,i] = 1
return a
def empty(shape, dtype=None, order=None):
#""Return a new array of given shape and type, without initializing entries
#""
return ndarray(shape, dtype, order=order)
def arange(*args, **kwargs):
#"" arange([start,] stop[, step,], dtype=None)
#Return evenly spaced values within a given interval.
#Values are generated within the half-open interval ``[start, stop)``
#(in other words, the interval including `start` but excluding `stop`).
#For integer arguments the function is equivalent to the Python built-in
#`range <http://docs.python.org/lib/built-in-funcs.html>`_ function,
#but returns an ndarray rather than a list.
#When using a non-integer step, such as 0.1, the results will often not
#be consistent. It is better to use ``linspace`` for these cases.
#""
# Get dtype
dtype = kwargs.pop('dtype', None)
if kwargs:
x = list(kwargs.keys())[0]
raise TypeError('arange() got an unexpected keyword argument %r' % x)
# Parse start, stop, step
if len(args) == 0:
raise TypeError('Required argument "start" not found')
elif len(args) == 1:
start, stop, step = 0, int(args[0]), 1
elif len(args) == 2:
start, stop, step = int(args[0]), int(args[1]), 1
elif len(args) == 3:
start, stop, step = int(args[0]), int(args[1]), int(args[2])
else:
raise TypeError('Too many input arguments')
# Init
iter = xrange(start, stop, step)
a = empty((len(iter),), dtype=dtype)
a[:] = list(iter)
return a
def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None):
#"" linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
#Return evenly spaced numbers over a specified interval. Returns num
#evenly spaced samples, calculated over the interval [start, stop].
#The endpoint of the interval can optionally be excluded.
#""
# Prepare
start, stop = float(start), float(stop)
ra = stop - start
if endpoint:
step = ra / (num-1)
else:
step = ra / num
# Create
a = empty((num,), dtype)
a[:] = [start + i * step for i in xrange(num)]
# Return
if retstep:
return a, step
else:
return a
def add(ndarray_vec1, ndarray_vec2):
c = []
for a, b in zip(ndarray_vec1, ndarray_vec2):
c.append(a+b)
cRay = array(c)
return cRay
def subtract(ndarray_vec1, ndarray_vec2):
c = []
for a, b in zip(ndarray_vec1, ndarray_vec2):
c.append(a-b)
cRay = array(c)
return cRay
def multiply(ndarray_vec1, ndarray_vec2):
c = []
for a, b in zip(ndarray_vec1, ndarray_vec2):
c.append(a*b)
cRay = array(c)
return cRay
def divide(ndarray_vec1, integer):
c = []
for a in ndarray_vec1:
c.append(a / integer)
cRay = array(c)
return cRay
def cross(u, v):
#""
#Return the cross product of two 2 or 3 dimensional vectors.
#""
uDim = len(u)
vDim = len(v)
uxv = []
# http://mathworld.wolfram.com/CrossProduct.html
if uDim == vDim == 2:
try:
uxv = [u[0]*v[1]-u[1]*v[0]]
except LinAlgError as e:
uxv = e
elif uDim == vDim == 3:
try:
for i in range(uDim):
uxv = [u[1]*v[2]-u[2]*v[1], -(u[0]*v[2]-u[2]*v[0]),
u[0]*v[1]-u[1]*v[0]]
except LinAlgError as e:
uxv = e
else:
raise IndexError('Vector has invalid dimensions')
return uxv
def dot(u, v):
#""
#Return the dot product of two equal-dimensional vectors.
#""
uDim = len(u)
vDim = len(v)
# http://reference.wolfram.com/language/ref/Dot.html
if uDim == vDim:
try:
u_dot_v = sum(map(operator.mul, u, v))
except LinAlgError as e:
u_dot_v = e
else:
raise IndexError('Vector has invalid dimensions')
return u_dot_v
def reshape(X,shape):
#""
#Returns the reshaped image of an ndarray
#""
assert isinstance(X, ndarray)
assert isinstance(shape, tuple) or isinstance(shape, list)
return X.reshape(shape)
## The class
class ndarray(object):
#"" ndarray(shape, dtype='float64', buffer=None, offset=0,
# strides=None, order=None)
#Array class similar to numpy's ndarray, implemented in pure Python.
#This class can be distinguished from a real numpy array in that
#the repr always shows the dtype as a string, and for larger arrays
#(more than 100 elements) it shows a short one-line repr.
#An array object represents a multidimensional, homogeneous array
#of fixed-size items. An associated data-type property describes the
#format of each element in the array.
#Arrays should be constructed using `array`, `zeros` or `empty` (refer
#to the See Also section below). The parameters given here refer to
#a low-level method (`ndarray(...)`) for instantiating an array.
#Parameters
#----------
#shape : tuple of ints
# Shape of created array.
#dtype : data-type, optional
# Any object that can be interpreted as a numpy data type.
#buffer : object contaning data, optional
# Used to fill the array with data. If another ndarray is given,
# the underlying data is used. Can also be a ctypes.Array or any
# object that exposes the buffer interface.
#offset : int, optional
# Offset of array data in buffer.
#strides : tuple of ints, optional
# Strides of data in memory.
#order : {'C', 'F'}, optional NOT SUPPORTED
# Row-major or column-major order.
#Attributes
#----------
#T : ndarray
# Transpose of the array. In tinynumpy only supported for ndim <= 3.
#data : buffer
# The array's elements, in memory. In tinynumpy this is a ctypes array.
#dtype : str
# Describes the format of the elements in the array. In tinynumpy
# this is a string.
#flags : dict
# Dictionary containing information related to memory use, e.g.,
# 'C_CONTIGUOUS', 'OWNDATA', 'WRITEABLE', etc.
#flat : iterator object
# Flattened version of the array as an iterator. In tinynumpy
# the iterator cannot be indexed.
#size : int
# Number of elements in the array.
#itemsize : int
# The memory use of each array element in bytes.
#nbytes : int
# The total number of bytes required to store the array data,
# i.e., ``itemsize * size``.
#ndim : int
# The array's number of dimensions.
#shape : tuple of ints
# Shape of the array.
#strides : tuple of ints
# The step-size required to move from one element to the next in
# memory. For example, a contiguous ``(3, 4)`` array of type
# ``int16`` in C-order has strides ``(8, 2)``. This implies that
# to move from element to element in memory requires jumps of 2 bytes.
# To move from row-to-row, one needs to jump 8 bytes at a time
# (``2 * 4``).
#base : ndarray
# If the array is a view into another array, that array is its `base`
# (unless that array is also a view). The `base` array is where the
# array data is actually stored.
#__array_interface__ : dict
# Dictionary with low level array information. Used by numpy to
# turn into a real numpy array. Can also be used to give C libraries
# access to the data via ctypes.
#See Also
#--------
#array : Construct an array.
#zeros : Create an array, each element of which is zero.
#empty : Create an array, but leave its allocated memory unchanged (i.e.,
# it contains "garbage").
#Notes
#-----
#There are two modes of creating an array:
#1. If `buffer` is None, then only `shape`, `dtype`, and `order`
# are used.
#2. If `buffer` is an object exposing the buffer interface, then
# all keywords are interpreted.
#""
__slots__ = ['_dtype', '_shape', '_strides', '_itemsize',
'_offset', '_base', '_data']
def __init__(self, shape, dtype='float64', buffer=None, offset=0,
strides=None, order=None):
# Check order
if order is not None:
raise RuntimeError('ndarray order parameter is not supported')
# Check and set shape
try :
assert isinstance(shape, Iterable)
shape = tuple(shape)
except Exception as e:
raise AssertionError('The shape must be tuple or list')
assert all([isinstance(x, int) for x in shape])
self._shape = shape
# Check and set dtype
dtype = _convert_dtype(dtype) if (dtype is not None) else 'float64'
if dtype not in _known_dtypes:
raise TypeError('data type %r not understood' % dtype)
self._dtype = dtype
# Itemsize is directly derived from dtype
self._itemsize = int(_convert_dtype(dtype, 'short')[-1])
if buffer is None:
# New array
self._base = None
# Check and set offset and strides
assert offset == 0
self._offset = 0
assert strides is None
self._strides = _strides_for_shape(self._shape, self.itemsize)
else:
# Existing array
if isinstance(buffer, ndarray) and buffer.base is not None:
buffer = buffer.base
# Keep a reference to avoid memory cleanup
self._base = buffer
# for ndarray we use the data property
if isinstance(buffer, ndarray):
buffer = buffer.data
# Check and set offset
assert isinstance(offset, int) and offset >= 0
self._offset = offset
# Check and set strides
if strides is None:
strides = _strides_for_shape(shape, self.itemsize)
assert isinstance(strides, tuple)
assert all([isinstance(x, int) for x in strides])
assert len(strides) == len(shape)
self._strides = strides
# Define our buffer class
buffersize = self._strides[0] * self._shape[0] // self._itemsize
buffersize += self._offset
BufferClass = _convert_dtype(dtype, 'ctypes') * buffersize
# Create buffer
if buffer is None:
self._data = BufferClass()
elif isinstance(buffer, ctypes.Array):
self._data = BufferClass.from_address(ctypes.addressof(buffer))
else:
self._data = BufferClass.from_buffer(buffer)
@property
def __array_interface__(self):
#"" Allow converting to real numpy array, or pass pointer to C library
#http://docs.scipy.org/doc/numpy/reference/arrays.interface.html
#""
readonly = False
# typestr
typestr = '<' + _convert_dtype(self.dtype, 'short')
# Pointer
if isinstance(self._data, ctypes.Array):
ptr = ctypes.addressof(self._data)
elif hasattr(self._data, '__array_interface__'):
ptr, readonly = self._data.__array_interface__['data']
elif hasattr(self._data, 'buffer_info'): # Python's array.array
ptr = self._data.buffer_info()[0]
elif isinstance(self._data, bytes):
ptr = ctypes.cast(self._data, ctypes.c_void_p).value
readonly = True
else:
raise TypeError('Cannot get address to underlying array data')
ptr += self._offset * self.itemsize
#
return dict(version=3,
shape=self.shape,
typestr=typestr,
descr=[('', typestr)],
data=(ptr, readonly),
strides=self.strides,
#offset=self._offset,
#mask=None,
)
def __len__(self):
return self.shape[0]
def __getitem__(self, key):
offset, shape, strides = self._index_helper(key)
if not shape:
# Return scalar
return self._data[offset]
else:
# Return view
return ndarray(shape, self.dtype,
offset=offset, strides=strides, buffer=self)
def __setitem__(self, key, value):
# Get info for view
offset, shape, strides = self._index_helper(key)
# Is this easy?
if not shape:
self._data[offset] = value
return
# Create view to set data to
view = ndarray(shape, self.dtype,
offset=offset, strides=strides, buffer=self)
# Get data to set as a list (because getting slices from ctype
# arrays yield lists anyway). The list is our "contiguous array"
if isinstance(value, (float, int)):
value_list = [value] * view.size
elif isinstance(value, (tuple, list)):
value_list = value
else:
if not isinstance(value, ndarray):
value = array(value, copy=False)
value_list = value._toflatlist()
# Check if size match
if view.size != len(value_list):
raise ValueError('Number of elements in source does not match '
'number of elements in target.')
# Assign data in most efficient way that we can. This code
# looks for the largest semi-contiguous block: the block that
# we can access as a 1D array with a stepsize.
subviews = [view]
value_index = 0
count = 0
while subviews:
subview = subviews.pop(0)
step = _get_step(subview)
if step:
block = value_list[value_index:value_index+subview.size]
s = slice(subview._offset,
subview._offset + subview.size * step,
step)
view._data[s] = block
value_index += subview.size
count += 1
else:
for i in range(subview.shape[0]):
subviews.append(subview[i])
assert value_index == len(value_list)
def __float__(self):
if self.size == 1:
return float(self.data[self._offset])
else:
raise TypeError('Only length-1 arrays can be converted to scalar')
def __int__(self):
if self.size == 1:
return int(self.data[self._offset])
else:
raise TypeError('Only length-1 arrays can be converted to scalar')
def __repr__(self):
# If more than 100 elements, show short repr
if self.size > 100:
shapestr = 'x'.join([str(i) for i in self.shape])
return '<ndarray %s %s at 0x%x>' % (shapestr, self.dtype, id(self))
# Otherwise, try to show in nice way
def _repr_r(s, axis, offset):
axisindent = min(2, max(0, (self.ndim - axis - 1)))
if axis < len(self.shape):
s += '['
for k_index, k in enumerate(xrange(self.shape[axis])):
if k_index > 0:
s += ('\\n ' + ' ' * axis) * axisindent
offset_ = offset + k * self._strides[axis] // self.itemsize
s = _repr_r(s, axis+1, offset_)
if k_index < self.shape[axis] - 1:
s += ', '
s += ']'
else:
r = repr(self.data[offset])
if '.' in r:
r = ' ' + r
if r.endswith('.0'):
r = r[:-1]
s += r
return s
s = _repr_r('', 0, self._offset)
if self.dtype != 'float64' and self.dtype != 'int32':
return "array(" + s + ", dtype='%s')" % self.dtype
else:
return "array(" + s + ")"
def __eq__(self, other):
if other.__module__.split('.')[0] == 'numpy':
return other == self
else:
out = empty(self.shape, 'bool')
out[:] = [i1==i2 for (i1, i2) in zip(self.flat, other.flat)]
return out
def __add__(self, other):
'''classic addition
'''
if (isinstance(other, int) or isinstance(other, float)) :
out = empty(self.shape, self.dtype)
out[:] = [dat+other for dat in self._data]
return out
if (isinstance(other, ndarray)):
if self.shape == other.shape :
out = empty(self.shape, self.dtype)
out[:] = [i+j for (i,j) in zip(self.flat, other.flat)]
return out
def __radd__(self, other):
return self.__add__(other)
def __sub__(self, other):
if (isinstance(other, int) or isinstance(other, float)) :
out = empty(self.shape, self.dtype)
out[:] = [dat-other for dat in self._data]
return out
if (isinstance(other, ndarray)):
if self.shape == other.shape :
out = empty(self.shape, self.dtype)
out[:] = [i-j for (i,j) in zip(self.flat, other.flat)]
return out
def __rsub__(self, other):
return self.__sub__(other)
def __mul__(self, other):
'''multiply element-wise with array or float/scalar'''
if (isinstance(other, int) or isinstance(other, float)) :
out = empty(self.shape, self.dtype)
out[:] = [dat*other for dat in self._data]
return out
if (isinstance(other, ndarray)):
if self.shape == other.shape :
out = empty(self.shape, self.dtype)
out[:] = [i*j for (i,j) in zip(self.flat, other.flat)]
return out
def __rmul__(self, other):
return self.__mul__(other)
def __div__(self, other):
'''divide element-wise with array or float/scalar'''
if (isinstance(other, int) or isinstance(other, float)) :
if other == 0 : raise ZeroDivisionError
out = empty(self.shape, self.dtype)
out[:] = [dat/other for dat in self._data]
return out
if (isinstance(other, ndarray)):
if self.shape == other.shape :
out = empty(self.shape, self.dtype)
out[:] = [i/j for (i,j) in zip(self.flat, other.flat)]
return out
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __truediv__(self, other):
'''divide element-wise with array or float/scalar'''
if (isinstance(other, int) or isinstance(other, float)) :
if other == 0 : raise ZeroDivisionError
out = empty(self.shape, self.dtype)
out[:] = [dat/other for dat in self._data]
return out
if (isinstance(other, ndarray)):
if self.shape == other.shape :
out = empty(self.shape, self.dtype)
out[:] = [i/j for (i,j) in zip(self.flat, other.flat)]
return out
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __floordiv__(self, other):
'''divide element-wise with array or float/scalar'''
if (isinstance(other, int) or isinstance(other, float)) :
if other == 0 : raise ZeroDivisionError
out = empty(self.shape, self.dtype)
out[:] = [dat//other for dat in self._data]
return out
if (isinstance(other, ndarray)):
if self.shape == other.shape :
out = empty(self.shape, self.dtype)
out[:] = [i//j for (i,j) in zip(self.flat, other.flat)]
return out
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __mod__(self, other):
'''divide element-wise with array or float/scalar'''
if (isinstance(other, int) or isinstance(other, float)) :
out = empty(self.shape, self.dtype)
out[:] = [dat%other for dat in self._data]
return out
if (isinstance(other, ndarray)):
if self.shape == other.shape :
out = empty(self.shape, self.dtype)
out[:] = [i%j for (i,j) in zip(self.flat, other.flat)]
return out
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __pow__(self, other):
'''power of two arrays element-wise (of just float power)'''
if (isinstance(other, int) or isinstance(other, float)) :
out = empty(self.shape, self.dtype)
out[:] = [dat**other for dat in self._data]
return out
if (isinstance(other, ndarray)):
if self.shape == other.shape :
out = empty(self.shape, self.dtype)
out[:] = [i**j for (i,j) in zip(self.flat, other.flat)]
return out
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __iadd__(self, other):
'''Addition of other array or float in place with += operator
'''
if (isinstance(other, int) or isinstance(other, float)) :
for i in range(len(self._data)):
self._data[i]+=other
return self
if (isinstance(other, ndarray)):
if self.shape == other.shape :
for i in range(len(self._data)):
self._data[i]+=other._data[i]
return self
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __isub__(self, other):
'''Addition of other array or float in place with += operator
'''
if (isinstance(other, int) or isinstance(other, float)) :
for i in range(len(self._data)):
self._data[i]-=other
return self
if (isinstance(other, ndarray)):
if self.shape == other.shape :
for i in range(len(self._data)):
self._data[i]-=other._data[i]
return self
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __imul__(self, other):
'''multiplication woth other array or float in place with *= operator
'''
if (isinstance(other, int) or isinstance(other, float)) :
for i in range(len(self._data)):
self._data[i]*=other
return self
if (isinstance(other, ndarray)):
if self.shape == other.shape :
for i in range(len(self._data)):
self._data[i]*=other._data[i]
return self
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __idiv__(self, other):
'''Division of other array or float in place with /= operator
'''
if (isinstance(other, int) or isinstance(other, float)) :
if other == 0 : raise ZeroDivisionError
for i in range(len(self._data)):
self._data[i]/=other
return self
if (isinstance(other, ndarray)):
if self.shape == other.shape :
for i in range(len(self._data)):
self._data[i]/=other._data[i]
return self
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __itruediv__(self, other):
'''Division of other array or float in place with /= operator
'''
if (isinstance(other, int) or isinstance(other, float)) :
if other == 0 : raise ZeroDivisionError
for i in range(len(self._data)):
self._data[i]/=other
return self
if (isinstance(other, ndarray)):
if self.shape == other.shape :
for i in range(len(self._data)):
self._data[i]/=other._data[i]
return self
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __ifloordiv__(self, other):
'''Division of other array or float in place with /= operator
'''
if (isinstance(other, int) or isinstance(other, float)) :
if other == 0 : raise ZeroDivisionError
for i in range(len(self._data)):
self._data[i]//=other
return self
if (isinstance(other, ndarray)):
if self.shape == other.shape :
for i in range(len(self._data)):
self._data[i]//=other._data[i]
return self
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __imod__(self, other):
'''mod of other array or float in place with /= operator
'''
if (isinstance(other, int) or isinstance(other, float)) :
if other == 0 : raise ZeroDivisionError
for i in range(len(self._data)):
self._data[i]%=other
return self
if (isinstance(other, ndarray)):
if self.shape == other.shape :
for i in range(len(self._data)):
self._data[i]%=other._data[i]
return self
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __imod__(self, other):
'''mod of other array or float in place with /= operator
'''
if (isinstance(other, int) or isinstance(other, float)) :
if other == 0 : raise ZeroDivisionError
for i in range(len(self._data)):
self._data[i]%=other
return self
if (isinstance(other, ndarray)):
if self.shape == other.shape :
for i in range(len(self._data)):
self._data[i]%=other._data[i]
return self
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
def __ipow__(self, other):
'''mod of other array or float in place with /= operator
'''
if (isinstance(other, int) or isinstance(other, float)) :
for i in range(len(self._data)):
self._data[i]**=other
return self
if (isinstance(other, ndarray)):
if self.shape == other.shape :
for i in range(len(self._data)):
self._data[i]**=other._data[i]
return self
else :
raise ValueError('Array sizes do not match. '+str(self.shape)\
+' versus '+str(other.shape))
## Private helper functions
def _index_helper(self, key):
# Indexing spec is located at:
# http://docs.scipy.org/doc/numpy/reference/arrays.indexing.html
# Promote to tuple.
if not isinstance(key, tuple):
key = (key,)
axis = 0
shape = []
strides = []
offset = self._offset
for k in key:
axissize = self._shape[axis]
if isinstance(k, int):
if k >= axissize:
raise IndexError('index %i is out of bounds for axis %i '
'with size %s' % (k, axis, axissize))
offset += k * self._strides[axis] // self.itemsize
axis += 1
elif isinstance(k, slice):
start, stop, step = k.indices(self.shape[axis])
shape.append(_ceildiv(stop - start, step))
strides.append(step * self._strides[axis])
offset += start * self._strides[axis] // self.itemsize
axis += 1
elif k is Ellipsis:
raise TypeError("ellipsis are not supported.")
elif k is None:
shape.append(1)
stride = 1
for s in self._strides[axis:]:
stride *= s
strides.append(stride)
else:
raise TypeError("key elements must be instaces of int or slice.")
shape.extend(self.shape[axis:])
strides.extend(self._strides[axis:])
return offset, tuple(shape), tuple(strides)
def _toflatlist(self):
value_list = []
subviews = [self]
count = 0
while subviews:
subview = subviews.pop(0)
step = _get_step(subview)
if step:
s = slice(subview._offset,
subview._offset + subview.size * step,
step)
value_list += self._data[s]
count += 1
else:
for i in range(subview.shape[0]):
subviews.append(subview[i])
return value_list
## Properties
@property
def ndim(self):
return len(self._shape)
@property
def size(self):
return _size_for_shape(self._shape)
@property
def nbytes(self):
return _size_for_shape(self._shape) * self.itemsize
def _get_shape(self):
return self._shape
def _set_shape(self, newshape):
if newshape == self.shape:
return
if self.size != _size_for_shape(newshape):
raise ValueError('Total size of new array must be unchanged')
if _get_step(self) == 1:
# Contiguous, hooray!
self._shape = tuple(newshape)
self._strides = _strides_for_shape(self._shape, self.itemsize)
return
# Else, try harder ... This code supports adding /removing
# singleton dimensions. Although it may sometimes be possible
# to split a dimension in two if the contiguous blocks allow
# this, we don't bother with such complex cases for now.
# Squeeze shape / strides
N = self.ndim
shape = [self.shape[i] for i in range(N) if self.shape[i] > 1]
strides = [self.strides[i] for i in range(N) if self.shape[i] > 1]
# Check if squeezed shapes match
newshape_ = [newshape[i] for i in range(len(newshape))
if newshape[i] > 1]
if newshape_ != shape:
raise AttributeError('incompatible shape for non-contiguous array')
# Modify to make this data work in loop
strides.append(strides[-1])
shape.append(1)
# Form new strides
i = -1
newstrides = []
try:
for s in reversed(newshape):
if s == 1:
newstrides.append(strides[i] * shape[i])
else:
i -= 1
newstrides.append(strides[i])
except IndexError:
# Fail
raise AttributeError('incompatible shape for non-contiguous array')
else:
# Success
newstrides.reverse()
self._shape = tuple(newshape)
self._strides = tuple(newstrides)
shape = property(_get_shape, _set_shape) # Python 2.5 compat (e.g. Jython)
@property
def strides(self):
return self._strides
@property
def dtype(self):
return self._dtype
@property
def itemsize(self):
return self._itemsize
@property
def base(self):
return self._base
@property
def data(self):
return self._data
@property
def flat(self):
subviews = [self]
count = 0
while subviews:
subview = subviews.pop(0)
step = _get_step(subview)
if step:
s = slice(subview._offset,
subview._offset + subview.size * step,
step)
for i in self._data[s]:
yield i
else:
for i in range(subview.shape[0]):
subviews.append(subview[i])
@property
def T(self):
if self.ndim < 2:
return self
else:
return self.transpose()
@property
def flags(self):
c_cont = _get_step(self) == 1
return dict(C_CONTIGUOUS=c_cont,
F_CONTIGUOUS=(c_cont and self.ndim < 2),
OWNDATA=(self._base is None),
WRITEABLE=True, # todo: fix this
ALIGNED=c_cont, # todo: different from contiguous?
UPDATEIFCOPY=False, # We don't support this feature
)
## Methods - managemenet
def fill(self, value):
assert isinstance(value, (int, float))
self[:] = value
def clip(self, a_min, a_max, out=None):
if out is None:
out = empty(self.shape, self.dtype)
L = self._toflatlist()
L = [min(a_max, max(a_min, x)) for x in L]
out[:] = L
return out
def copy(self):
out = empty(self.shape, self.dtype)
out[:] = self
return out
def flatten(self):
out = empty((self.size,), self.dtype)
out[:] = self
return out
def ravel(self):
return self.reshape((self.size, ))
def repeat(self, repeats, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
out = empty((self.size * repeats,), self.dtype)
for i in range(repeats):
out[i*self.size:(i+1)*self.size] = self
return out
def reshape(self, newshape):
out = self.view()
try:
out.shape = newshape
except AttributeError:
out = self.copy()
out.shape = newshape
return out
def transpose(self):
# Numpy returns a view, but we cannot do that since we do not
# support Fortran ordering
ndim = self.ndim
if ndim < 2:
return self.view()
shape = self.shape[::-1]
out = empty(shape, self.dtype)
#
if ndim == 2:
for i in xrange(self.shape[0]):
out[:, i] = self[i, :]
elif ndim == 3:
for i in xrange(self.shape[0]):
for j in xrange(self.shape[1]):
out[:, j, i] = self[i, j, :]
else:
raise ValueError('Tinynumpy supports transpose up to ndim=3')
return out
def astype(self, dtype):
out = empty(self.shape, dtype)
out[:] = self
def view(self, dtype=None, type=None):
if dtype is None:
dtype = self.dtype
if dtype == self.dtype:
return ndarray(self.shape, dtype, buffer=self,
offset=self._offset, strides=self.strides)
elif self.ndim == 1:
itemsize = int(_convert_dtype(dtype, 'short')[-1])
size = self.nbytes // itemsize
offsetinbytes = self._offset * self.itemsize
offset = offsetinbytes // itemsize
return ndarray((size, ), dtype, buffer=self, offset=offset)
else:
raise ValueError('new type not compatible with array.')
## Methods - statistics
# We use the self.flat generator here. self._toflatlist() would be
# faster, but it might take up significantly more memory.
def all(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
return all(self.flat)
def any(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
return any(self.flat)
def min(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
return min(self.flat)
def max(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
return max(self.flat)
#return max(self._toflatlist()) # almost twice as fast
def sum(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
return sum(self.flat)
def prod(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
p = 1.0
for i in self.flat:
p *= float(i)
return p
def ptp(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
mn = self.data[self._offset]
mx = mn
for i in self.flat:
if i > mx:
mx = i
if i < mn:
mn = i
return mx - mn
def mean(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
return self.sum() / self.size
def argmax(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
r = self.data[self._offset]
r_index = 0
for i_index, i in enumerate(self.flat):
if i > r:
r = i
r_index = i_index
return r_index
def argmin(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
r = self.data[self._offset]
r_index = 0
for i_index, i in enumerate(self.flat):
if i < r:
r = i
r_index = i_index
return r_index
def cumprod(self, axis=None, out=None):
if axis:
raise (TypeError, "axis argument is not supported")
if out is None:
out = empty((self.size,), self.dtype)
p = 1
L = []
for x in self.flat:
p *= x
L.append(p)
out[:] = L
return out
def cumsum(self, axis=None, out=None):
if axis:
raise (TypeError, "axis argument is not supported")
if out is None:
out = empty((self.size,), self.dtype)
p = 0
L = []
for x in self.flat:
p += x
L.append(p)
out[:] = L
return out
def var(self, axis=None):
if axis:
raise (TypeError, "axis argument is not supported")
m = self.mean()
acc = 0
for x in self.flat:
acc += abs(x - m) ** 2
return acc / self.size
def std(self, axis=None):
return sqrt(self.var(axis))
def argwhere(self, val):
#assumes that list has only values of same dtype
idx = [i for i, e in enumerate(self.flat) if e == val]
keys = [list(_key_for_index(i, self.shape)) for i in idx]
return keys
def tolist(self):
'''
Returns the ndarray as a comprehensive list
'''
#shp = list(self.shape).copy()
shp = [x for x in self.shape]
jump = self.size//shp[-1]
n_comp = 0 #comprehension depth
#comp = list(self._data).copy()
comp = [x for x in self._data]
while n_comp < len(self.shape)-1 :
comp = [comp[i*shp[-1]:i*shp[-1]+shp[-1]] for i in range(jump)]
shp.pop()
jump = len(comp)//shp[-1]
n_comp +=1
return comp
class nditer:
def __init__(self, array):
self.array = array
self.key = [0] * len(self.array.shape)
def __iter__(self):
return self
def __len__(self):
return _size_for_shape(self.array.shape)
def __getitem__(self, index):
key = _key_for_index(index, self.array.shape)
return self.array[key]
def __next__(self):
if self.key is None:
raise StopIteration
value = self.array[tuple(self.key)]
if not _increment_mutable_key(self.key, self.array.shape):
self.key = None
return value
def next(self):
return self.__next__()
"""
import types
import sys
numpy = types.ModuleType('numpy')
sys.modules['numpy'] = numpy
exec(numpy_code, numpy.__dict__)
np = numpy
np.float16 = "float16"
import gdb
# import numpy as np
# from enum import Enum
# np.set_printoptions(suppress=True) # prevent numpy exponential notation on print, default False
# np.set_printoptions(threshold=sys.maxsize)
def conv(obj, t):
# return gdb.parse_and_eval(f'({t})({obj})')
s = "({})({})".format(t, obj)
return gdb.parse_and_eval(s)
def booli(obj):
return conv(str(obj).lower(), 'bool')
def stri(obj):
#s = f'"{obj}"'
s = "\"{}\"".format(obj)
#return conv(s.translate(s.maketrans('\n', ' ')), 'char*')
replaced = s.replace("\n", " ")
return replaced
# class MagicValues(Enum):
class MagicValues():
MAGIC_VAL = 0x42FF0000
AUTO_STEP = 0
CONTINUOUS_FLAG = 1 << 14
SUBMATRIX_FLAG = 1 << 15
# class MagicMasks(Enum):
class MagicMasks():
MAGIC_MASK = 0xFFFF0000
TYPE_MASK = 0x00000FFF
DEPTH_MASK = 7
# class Depth(Enum):
class Depth():
CV_8U = 0
CV_8S = 1
CV_16U = 2
CV_16S = 3
CV_32S = 4
CV_32F = 5
CV_64F = 6
CV_16F = 7
def create_enum(n):
def make_type(depth, cn):
return depth["value"] + ((cn - 1) << 3)
# defs = [(f'{depth.name}C{i}', make_type(depth, i)) for depth in Depth for i in range(1, n + 1)]
Depthlist = [
{"value": Depth.CV_8U, "name": "CV_8U"},
{"value": Depth.CV_8S, "name": "CV_8S"} ,
{"value": Depth.CV_16U, "name": "CV_16U"} ,
{"value": Depth.CV_16S, "name": "CV_16S"} ,
{"value": Depth.CV_32S, "name": "CV_32S"} ,
{"value": Depth.CV_32F, "name": "CV_32F"} ,
{"value": Depth.CV_64F, "name": "CV_64F"} ,
{"value": Depth.CV_16F, "name": "CV_16F"} ,
]
defs = [("{}C{}".format(depth["name"], i), make_type(depth, i)) for depth in Depthlist for i in range(1, n + 1)]
#return Enum('Type', defs)
return defs
Type = create_enum(512)
class Flags:
def depth(self):
#return Depth(self.flags & MagicMasks.DEPTH_MASK.value)
return self.flags & MagicMasks.DEPTH_MASK
def dtype(self):
depth = self.depth()
ret = None
if depth == Depth.CV_8U:
ret = (np.uint8, 'uint8_t')
elif depth == Depth.CV_8S:
ret = (np.int8, 'int8_t')
elif depth == Depth.CV_16U:
ret = (np.uint16, 'uint16_t')
elif depth == Depth.CV_16S:
ret = (np.int16, 'int16_t')
elif depth == Depth.CV_32S:
ret = (np.int32, 'int32_t')
elif depth == Depth.CV_32F:
ret = (np.float32, 'float')
elif depth == Depth.CV_64F:
ret = (np.float64, 'double')
elif depth == Depth.CV_16F:
ret = (np.float16, 'float16')
return ret
def type(self):
# return Type(self.flags & MagicMasks.TYPE_MASK.value)
return Type[self.flags & MagicMasks.TYPE_MASK]
def channels(self):
return ((self.flags & (511 << 3)) >> 3) + 1
def is_continuous(self):
#return (self.flags & MagicValues.CONTINUOUS_FLAG.value) != 0
return (self.flags & MagicValues.CONTINUOUS_FLAG) != 0
def is_submatrix(self):
#return (self.flags & MagicValues.SUBMATRIX_FLAG.value) != 0
return (self.flags & MagicValues.SUBMATRIX_FLAG) != 0
def __init__(self, flags):
self.flags = flags
def __iter__(self):
itmss = {}
itmss['type'] = stri(self.type()[0])
itmss['is_continuous']= booli(self.is_continuous())
itmss['is_submatrix']= booli(self.is_submatrix())
return iter(itmss.items())
#return iter({
# 'type': stri(self.type().name),
# 'is_continuous': booli(self.is_continuous()),
# 'is_submatrix': booli(self.is_submatrix())
# }.items())
class Size:
def __init__(self, ptr):
self.ptr = ptr
def dims(self):
return int((self.ptr - 1).dereference())
def to_numpy(self):
return np.array([int(self.ptr[i]) for i in range(self.dims())], dtype=np.int64)
def __iter__(self):
return iter({'size': stri(self.to_numpy())}.items())
class Mat:
def __init__(self, m, size, flags):
(dtype, ctype) = flags.dtype()
#elsize = np.dtype(dtype).itemsize
elsize = int(np._convert_dtype(dtype)[-1])
ptr = m['data']
#dataptr = int(ptr)
dataptr = int(ptr.cast(gdb.lookup_type("uint64")))
#length = (int(m['dataend']) - dataptr) // elsize
length = (int(m['dataend'].cast(gdb.lookup_type("uint64"))) - dataptr) // elsize
#start = (int(m['datastart']) - dataptr) // elsize
start = (int(m['datastart'].cast(gdb.lookup_type("uint64"))) - dataptr) // elsize
if length == 0:
self.mat = np.array([])
self.view = self.mat
return
if dtype != np.float16:
ctype = gdb.lookup_type(ctype)
ptr = ptr.cast(ctype.array(length - 1).pointer()).dereference()
self.mat = np.array([ptr[i] for i in range(length)], dtype=dtype)
else:
u16 = gdb.lookup_type('uint16_t')
ptr = ptr.cast(u16.array(length - 1).pointer()).dereference()
self.mat = np.array([ptr[i] for i in range(length)], dtype=np.uint16)
#self.mat = self.mat.view(np.float16)
#steps = np.asarray([int(m['step']['p'][i]) for i in range(size.dims())], dtype=np.int64)
steps = np.array([int(m['step']['p'][i]) for i in range(size.dims())], dtype=np.int64)
#self.view = np.lib.stride_tricks.as_strided(self.mat[start:], shape=size.to_numpy(), strides=steps)
shape = [int(x) for x in size.to_numpy().tolist()]
strides = tuple([int(x) for x in steps.tolist()])
self.view = np.ndarray(shape, dtype=self.mat.dtype, buffer=self.mat, offset=start, strides=strides)
def __iter__(self):
return iter({'data': stri(self.view)}.items())
class MatPrinter:
"""Print a cv::Mat"""
def __init__(self, mat):
self.mat = mat
def to_string(self):
start = int(self.mat['datastart'].cast(gdb.lookup_type("uint64")))
return "cv::Mat 0x{:x}".format(start)
def views(self):
m = self.mat
flags = Flags(int(m['flags']))
size = Size(m['size']['p'])
data = Mat(m, size, flags)
for x in [flags, size, data]:
for k, v in x:
yield 'view_' + k, v
def real(self):
m = self.mat
for field in m.type.fields():
k = field.name
v = m[k]
yield k, v
# TODO: add an enum in interface.h with all cv::Mat element types and use that instead
# yield 'test', gdb.parse_and_eval(f'(cv::MatTypes)0')
def children(self): # TODO: hide real members under new child somehow
# yield from self.views()
#return []
for k, v in self.views():
yield k, v
# yield from self.real()
#for k, v in self.real():
# yield k, v
def get_type(val):
# Get the type.
vtype = val.type
# If it points to a reference, get the reference.
if vtype.code == gdb.TYPE_CODE_REF:
vtype = vtype.target()
# Get the unqualified type, stripped of typedefs.
vtype = vtype.unqualified().strip_typedefs()
# Get the type name.
typename = vtype.tag
return typename
def mat_printer(val):
typename = get_type(val)
if typename is None:
return None
if str(typename) == 'cv::Mat':
return MatPrinter(val)
gdb.pretty_printers.append(mat_printer)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment