Module nux.tensor

Tensor object and its utilities

Expand source code
"""Tensor object and its utilities"""

import ctypes
from ctypes import c_void_p, POINTER, c_uint64, c_uint8, byref
from enum import IntEnum
from typing import Union

import numpy as np

from nux._util import list_to_dict
from nux.errors import UnsupportedTensorType
from ._api import LIBNUX


class Axis(IntEnum):
    """Axis of Tensor"""
    WIDTH = 0
    HEIGHT = 1
    CHANNEL = 2
    BATCH = 3
    WIDTH_OUTER = 4
    HEIGHT_OUTER = 5
    CHANNEL_OUTER = 6
    BATCH_OUTER = 7

    @classmethod
    def _names(cls):
        return ["W", "H", "C", "N", "Wo", "Ho", "Co", "No"]

    def __repr__(self):
        return self._names()[self]


class DataType(IntEnum):
    """Tensor data type"""
    FLOAT32 = 0
    UINT8 = 1
    INT8 = 2
    INT32 = 3

    @classmethod
    def _names(cls):
        return ["float32", "uint8", "int8", "int32"]

    @classmethod
    def _numpy_dtypes(cls):
        return [np.float32, np.uint8, np.int8, np.int32]

    def __repr__(self) -> str:
        return self._names()[self]

    def numpy_dtype(self):
        """Return the numpy dtype corresponding to this DataType"""
        return self._numpy_dtypes()[self]


class TensorDesc:
    """Tensor description including dimension, shape, and data type"""
    ref = c_void_p(None)

    def __init__(self, ref):
        self.ref = ref
        self._as_parameter_ = ref

    def ndim(self) -> int:
        """Number of dimensions"""
        return LIBNUX.nux_tensor_dim_num(self)

    def dim(self, idx) -> int:
        """Size of i-th dimension"""
        return LIBNUX.nux_tensor_dim(self, idx)

    def shape(self) -> tuple:
        """tensor shape"""
        dims = []
        for i in range(self.ndim()):
            dims.append(self.dim(i))

        return tuple(dims)

    def axis(self, idx) -> Axis:
        """Axis type of i-th dimension (e.g., width, height, channel)"""
        return Axis(LIBNUX.nux_tensor_axis(self, idx))

    def size(self) -> int:
        """Size in bytes"""
        return LIBNUX.nux_tensor_size(self)

    def length(self) -> int:
        """Number of all elements across all dimensions"""
        return LIBNUX.nux_tensor_len(self)

    def format(self) -> str:
        """Tensor memory layout (e.g., NHWC, NCHW)"""
        tensor_format = str()
        for idx in range(self.ndim()):
            tensor_format += self.axis(idx).__repr__()

        return tensor_format

    def dtype(self) -> DataType:
        """Data type of tensor"""
        return DataType(LIBNUX.nux_tensor_dtype(self))

    def numpy_dtype(self):
        """Return numpy dtype"""
        return self.dtype().numpy_dtype()

    def __repr__(self) -> str:
        return self.__class__.__name__ + \
               ': shape=' + str(self.shape()) + \
               ', dtype=' + self.dtype().__repr__() + \
               ', format=' + self.format() + \
               ', size=' + str(self.size()) + \
               ', len=' + str(self.length())


class Tensor:
    """A tensor which contains data and tensor description including shape"""
    ref = c_void_p(None)
    allocated: bool = False
    desc = TensorDesc

    def __init__(self, ref, desc: TensorDesc, allocated=False):
        self.ref = ref
        self.desc = desc
        self.allocated = allocated
        self._as_parameter_ = ref

    def shape(self) -> tuple:
        """Return the tensor shape

        Returns:
            Tensor shape. An example shape is
            ```(1, 28, 28, 1)```.
        """
        return self.desc.shape()

    def dtype(self) -> DataType:
        """Data type of tensor"""
        return self.desc.dtype()

    def numpy_dtype(self):
        """Return numpy dtype"""
        return self.desc.numpy_dtype()

    def copy_from(self, data: Union[np.ndarray, np.generic]):
        """Copy the contents of Numpy ndarray to this tensor"""
        if isinstance(data, (np.ndarray, np.generic)):
            buf_ptr = data.ctypes.data_as(POINTER(c_uint8))
            buf_size_in_bytes = data.nbytes
            LIBNUX.tensor_fill_buffer(self, buf_ptr, buf_size_in_bytes)
        else:
            raise UnsupportedTensorType()

    def numpy(self) -> np.ndarray:
        """Return numpy.ndarray converted from this tensor"""
        itemsize = np.dtype(self.numpy_dtype()).itemsize
        arr_size = np.prod(self.shape()[:]) * itemsize

        buf_ptr = POINTER(c_uint8)()
        buf_len = c_uint64(0)
        LIBNUX.tensor_get_buffer(self, byref(buf_ptr), byref(buf_len))

        buf_from_mem = ctypes.pythonapi.PyMemoryView_FromMemory
        buf_from_mem.restype = ctypes.py_object
        buf_from_mem.argtypes = (ctypes.c_void_p, ctypes.c_int, ctypes.c_int)
        buffer = buf_from_mem(buf_ptr, arr_size, 0x100)

        arr = np.ndarray(tuple(self.shape()[:]), self.numpy_dtype(), buffer, order="C")
        return arr.copy()

    def __repr__(self):
        return '<' + self.__class__.__name__ + \
               ': shape=' + str(self.desc.shape()) + \
               ', dtype=' + str(self.desc.dtype()) + \
               ', numpy=' + str(self.numpy()) + '>'

    def __del__(self):
        if self.allocated and self.ref:
            LIBNUX.nux_tensor_destroy(self.ref)
            self.allocated = False

    def __eq__(self, other):
        if isinstance(other, Tensor):
            return np.array_equal(self.numpy(), other.numpy())

        return False


class TensorArray:
    """A list of tensors

    It is used for input and output values of model inferences.
    """
    ref = c_void_p(None)
    should_drop: bool = False
    descs: [TensorDesc]
    len: int

    def __init__(self, ref, descs: [TensorDesc], allocated=False):
        self.ref = ref
        self.descs = descs
        self.should_drop = allocated
        self._as_parameter_ = ref
        self.len = LIBNUX.nux_tensor_array_len(self.ref)

    def is_empty(self) -> bool:
        """True if it has no Tensor"""
        return self.len == 0

    def __len__(self):
        return self.len

    def __getitem__(self, key):
        if isinstance(key, int):
            while key < 0:
                # Index is a negative, so addition will subtract.
                key += len(self)

            if key >= len(self):
                raise IndexError("tensor index (%d) out of range [0, %d)" % (key, len(self)))

            return Tensor(LIBNUX.nux_tensor_array_get(self, key),
                          desc=self.descs[key], allocated=False)

        if isinstance(key, slice):
            start, stop, step = key.indices(len(self))
            return [self[i] for i in range(start, stop, step)]

        raise TypeError

    def __setitem__(self, key, data):
        if isinstance(key, int):
            while key < 0:
                # Index is a negative, so addition will subtract.
                key += len(self)

            if key >= len(self):
                raise IndexError("tensor index (%d) out of range [0, %d)" % (key, len(self)))

            self[key].copy_from(data)
            return

        raise TypeError

    def numpy(self) -> [np.ndarray]:
        """Convert TensorArray to a list of numpy.ndarray"""
        array = []
        for idx in range(self.len):
            array.append(self[idx].numpy())

        return array

    def __del__(self):
        if self.should_drop and self.ref:
            LIBNUX.nux_tensor_array_destroy(self.ref)
            self.should_drop = False

    def __repr__(self):
        return list_to_dict(self).__repr__()


def numpy_dtype(value):
    """Return numpy dtype from any eligible object of Nux"""
    if isinstance(value, (np.ndarray, np.generic)):
        return value.dtype()
    if isinstance(value, Tensor):
        return value.numpy_dtype()
    if isinstance(value, TensorDesc):
        return value.numpy_dtype()
    if isinstance(value, DataType):
        return value.numpy_dtype()

    raise TypeError

Functions

def numpy_dtype(value)

Return numpy dtype from any eligible object of Nux

Expand source code
def numpy_dtype(value):
    """Return numpy dtype from any eligible object of Nux"""
    if isinstance(value, (np.ndarray, np.generic)):
        return value.dtype()
    if isinstance(value, Tensor):
        return value.numpy_dtype()
    if isinstance(value, TensorDesc):
        return value.numpy_dtype()
    if isinstance(value, DataType):
        return value.numpy_dtype()

    raise TypeError

Classes

class Axis (value, names=None, *, module=None, qualname=None, type=None, start=1)

Axis of Tensor

Expand source code
class Axis(IntEnum):
    """Axis of Tensor"""
    WIDTH = 0
    HEIGHT = 1
    CHANNEL = 2
    BATCH = 3
    WIDTH_OUTER = 4
    HEIGHT_OUTER = 5
    CHANNEL_OUTER = 6
    BATCH_OUTER = 7

    @classmethod
    def _names(cls):
        return ["W", "H", "C", "N", "Wo", "Ho", "Co", "No"]

    def __repr__(self):
        return self._names()[self]

Ancestors

  • enum.IntEnum
  • builtins.int
  • enum.Enum

Class variables

var BATCH
var BATCH_OUTER
var CHANNEL
var CHANNEL_OUTER
var HEIGHT
var HEIGHT_OUTER
var WIDTH
var WIDTH_OUTER
class DataType (value, names=None, *, module=None, qualname=None, type=None, start=1)

Tensor data type

Expand source code
class DataType(IntEnum):
    """Tensor data type"""
    FLOAT32 = 0
    UINT8 = 1
    INT8 = 2
    INT32 = 3

    @classmethod
    def _names(cls):
        return ["float32", "uint8", "int8", "int32"]

    @classmethod
    def _numpy_dtypes(cls):
        return [np.float32, np.uint8, np.int8, np.int32]

    def __repr__(self) -> str:
        return self._names()[self]

    def numpy_dtype(self):
        """Return the numpy dtype corresponding to this DataType"""
        return self._numpy_dtypes()[self]

Ancestors

  • enum.IntEnum
  • builtins.int
  • enum.Enum

Class variables

var FLOAT32
var INT32
var INT8
var UINT8

Methods

def numpy_dtype(self)

Return the numpy dtype corresponding to this DataType

Expand source code
def numpy_dtype(self):
    """Return the numpy dtype corresponding to this DataType"""
    return self._numpy_dtypes()[self]
class Tensor (ref, desc: TensorDesc, allocated=False)

A tensor which contains data and tensor description including shape

Expand source code
class Tensor:
    """A tensor which contains data and tensor description including shape"""
    ref = c_void_p(None)
    allocated: bool = False
    desc = TensorDesc

    def __init__(self, ref, desc: TensorDesc, allocated=False):
        self.ref = ref
        self.desc = desc
        self.allocated = allocated
        self._as_parameter_ = ref

    def shape(self) -> tuple:
        """Return the tensor shape

        Returns:
            Tensor shape. An example shape is
            ```(1, 28, 28, 1)```.
        """
        return self.desc.shape()

    def dtype(self) -> DataType:
        """Data type of tensor"""
        return self.desc.dtype()

    def numpy_dtype(self):
        """Return numpy dtype"""
        return self.desc.numpy_dtype()

    def copy_from(self, data: Union[np.ndarray, np.generic]):
        """Copy the contents of Numpy ndarray to this tensor"""
        if isinstance(data, (np.ndarray, np.generic)):
            buf_ptr = data.ctypes.data_as(POINTER(c_uint8))
            buf_size_in_bytes = data.nbytes
            LIBNUX.tensor_fill_buffer(self, buf_ptr, buf_size_in_bytes)
        else:
            raise UnsupportedTensorType()

    def numpy(self) -> np.ndarray:
        """Return numpy.ndarray converted from this tensor"""
        itemsize = np.dtype(self.numpy_dtype()).itemsize
        arr_size = np.prod(self.shape()[:]) * itemsize

        buf_ptr = POINTER(c_uint8)()
        buf_len = c_uint64(0)
        LIBNUX.tensor_get_buffer(self, byref(buf_ptr), byref(buf_len))

        buf_from_mem = ctypes.pythonapi.PyMemoryView_FromMemory
        buf_from_mem.restype = ctypes.py_object
        buf_from_mem.argtypes = (ctypes.c_void_p, ctypes.c_int, ctypes.c_int)
        buffer = buf_from_mem(buf_ptr, arr_size, 0x100)

        arr = np.ndarray(tuple(self.shape()[:]), self.numpy_dtype(), buffer, order="C")
        return arr.copy()

    def __repr__(self):
        return '<' + self.__class__.__name__ + \
               ': shape=' + str(self.desc.shape()) + \
               ', dtype=' + str(self.desc.dtype()) + \
               ', numpy=' + str(self.numpy()) + '>'

    def __del__(self):
        if self.allocated and self.ref:
            LIBNUX.nux_tensor_destroy(self.ref)
            self.allocated = False

    def __eq__(self, other):
        if isinstance(other, Tensor):
            return np.array_equal(self.numpy(), other.numpy())

        return False

Class variables

var allocated : bool
var desc

Tensor description including dimension, shape, and data type

var ref

Methods

def copy_from(self, data: Union[numpy.ndarray, numpy.generic])

Copy the contents of Numpy ndarray to this tensor

Expand source code
def copy_from(self, data: Union[np.ndarray, np.generic]):
    """Copy the contents of Numpy ndarray to this tensor"""
    if isinstance(data, (np.ndarray, np.generic)):
        buf_ptr = data.ctypes.data_as(POINTER(c_uint8))
        buf_size_in_bytes = data.nbytes
        LIBNUX.tensor_fill_buffer(self, buf_ptr, buf_size_in_bytes)
    else:
        raise UnsupportedTensorType()
def dtype(self) ‑> DataType

Data type of tensor

Expand source code
def dtype(self) -> DataType:
    """Data type of tensor"""
    return self.desc.dtype()
def numpy(self) ‑> numpy.ndarray

Return numpy.ndarray converted from this tensor

Expand source code
def numpy(self) -> np.ndarray:
    """Return numpy.ndarray converted from this tensor"""
    itemsize = np.dtype(self.numpy_dtype()).itemsize
    arr_size = np.prod(self.shape()[:]) * itemsize

    buf_ptr = POINTER(c_uint8)()
    buf_len = c_uint64(0)
    LIBNUX.tensor_get_buffer(self, byref(buf_ptr), byref(buf_len))

    buf_from_mem = ctypes.pythonapi.PyMemoryView_FromMemory
    buf_from_mem.restype = ctypes.py_object
    buf_from_mem.argtypes = (ctypes.c_void_p, ctypes.c_int, ctypes.c_int)
    buffer = buf_from_mem(buf_ptr, arr_size, 0x100)

    arr = np.ndarray(tuple(self.shape()[:]), self.numpy_dtype(), buffer, order="C")
    return arr.copy()
def numpy_dtype(self)

Return numpy dtype

Expand source code
def numpy_dtype(self):
    """Return numpy dtype"""
    return self.desc.numpy_dtype()
def shape(self) ‑> tuple

Return the tensor shape

Returns

Tensor shape. An example shape is (1, 28, 28, 1).

Expand source code
def shape(self) -> tuple:
    """Return the tensor shape

    Returns:
        Tensor shape. An example shape is
        ```(1, 28, 28, 1)```.
    """
    return self.desc.shape()
class TensorArray (ref, descs: [TensorDesc'>], allocated=False)

A list of tensors

It is used for input and output values of model inferences.

Expand source code
class TensorArray:
    """A list of tensors

    It is used for input and output values of model inferences.
    """
    ref = c_void_p(None)
    should_drop: bool = False
    descs: [TensorDesc]
    len: int

    def __init__(self, ref, descs: [TensorDesc], allocated=False):
        self.ref = ref
        self.descs = descs
        self.should_drop = allocated
        self._as_parameter_ = ref
        self.len = LIBNUX.nux_tensor_array_len(self.ref)

    def is_empty(self) -> bool:
        """True if it has no Tensor"""
        return self.len == 0

    def __len__(self):
        return self.len

    def __getitem__(self, key):
        if isinstance(key, int):
            while key < 0:
                # Index is a negative, so addition will subtract.
                key += len(self)

            if key >= len(self):
                raise IndexError("tensor index (%d) out of range [0, %d)" % (key, len(self)))

            return Tensor(LIBNUX.nux_tensor_array_get(self, key),
                          desc=self.descs[key], allocated=False)

        if isinstance(key, slice):
            start, stop, step = key.indices(len(self))
            return [self[i] for i in range(start, stop, step)]

        raise TypeError

    def __setitem__(self, key, data):
        if isinstance(key, int):
            while key < 0:
                # Index is a negative, so addition will subtract.
                key += len(self)

            if key >= len(self):
                raise IndexError("tensor index (%d) out of range [0, %d)" % (key, len(self)))

            self[key].copy_from(data)
            return

        raise TypeError

    def numpy(self) -> [np.ndarray]:
        """Convert TensorArray to a list of numpy.ndarray"""
        array = []
        for idx in range(self.len):
            array.append(self[idx].numpy())

        return array

    def __del__(self):
        if self.should_drop and self.ref:
            LIBNUX.nux_tensor_array_destroy(self.ref)
            self.should_drop = False

    def __repr__(self):
        return list_to_dict(self).__repr__()

Class variables

var descs : [TensorDesc'>]
var len : int
var ref
var should_drop : bool

Methods

def is_empty(self) ‑> bool

True if it has no Tensor

Expand source code
def is_empty(self) -> bool:
    """True if it has no Tensor"""
    return self.len == 0
def numpy(self) ‑> []

Convert TensorArray to a list of numpy.ndarray

Expand source code
def numpy(self) -> [np.ndarray]:
    """Convert TensorArray to a list of numpy.ndarray"""
    array = []
    for idx in range(self.len):
        array.append(self[idx].numpy())

    return array
class TensorDesc (ref)

Tensor description including dimension, shape, and data type

Expand source code
class TensorDesc:
    """Tensor description including dimension, shape, and data type"""
    ref = c_void_p(None)

    def __init__(self, ref):
        self.ref = ref
        self._as_parameter_ = ref

    def ndim(self) -> int:
        """Number of dimensions"""
        return LIBNUX.nux_tensor_dim_num(self)

    def dim(self, idx) -> int:
        """Size of i-th dimension"""
        return LIBNUX.nux_tensor_dim(self, idx)

    def shape(self) -> tuple:
        """tensor shape"""
        dims = []
        for i in range(self.ndim()):
            dims.append(self.dim(i))

        return tuple(dims)

    def axis(self, idx) -> Axis:
        """Axis type of i-th dimension (e.g., width, height, channel)"""
        return Axis(LIBNUX.nux_tensor_axis(self, idx))

    def size(self) -> int:
        """Size in bytes"""
        return LIBNUX.nux_tensor_size(self)

    def length(self) -> int:
        """Number of all elements across all dimensions"""
        return LIBNUX.nux_tensor_len(self)

    def format(self) -> str:
        """Tensor memory layout (e.g., NHWC, NCHW)"""
        tensor_format = str()
        for idx in range(self.ndim()):
            tensor_format += self.axis(idx).__repr__()

        return tensor_format

    def dtype(self) -> DataType:
        """Data type of tensor"""
        return DataType(LIBNUX.nux_tensor_dtype(self))

    def numpy_dtype(self):
        """Return numpy dtype"""
        return self.dtype().numpy_dtype()

    def __repr__(self) -> str:
        return self.__class__.__name__ + \
               ': shape=' + str(self.shape()) + \
               ', dtype=' + self.dtype().__repr__() + \
               ', format=' + self.format() + \
               ', size=' + str(self.size()) + \
               ', len=' + str(self.length())

Class variables

var ref

Methods

def axis(self, idx) ‑> Axis

Axis type of i-th dimension (e.g., width, height, channel)

Expand source code
def axis(self, idx) -> Axis:
    """Axis type of i-th dimension (e.g., width, height, channel)"""
    return Axis(LIBNUX.nux_tensor_axis(self, idx))
def dim(self, idx) ‑> int

Size of i-th dimension

Expand source code
def dim(self, idx) -> int:
    """Size of i-th dimension"""
    return LIBNUX.nux_tensor_dim(self, idx)
def dtype(self) ‑> DataType

Data type of tensor

Expand source code
def dtype(self) -> DataType:
    """Data type of tensor"""
    return DataType(LIBNUX.nux_tensor_dtype(self))
def format(self) ‑> str

Tensor memory layout (e.g., NHWC, NCHW)

Expand source code
def format(self) -> str:
    """Tensor memory layout (e.g., NHWC, NCHW)"""
    tensor_format = str()
    for idx in range(self.ndim()):
        tensor_format += self.axis(idx).__repr__()

    return tensor_format
def length(self) ‑> int

Number of all elements across all dimensions

Expand source code
def length(self) -> int:
    """Number of all elements across all dimensions"""
    return LIBNUX.nux_tensor_len(self)
def ndim(self) ‑> int

Number of dimensions

Expand source code
def ndim(self) -> int:
    """Number of dimensions"""
    return LIBNUX.nux_tensor_dim_num(self)
def numpy_dtype(self)

Return numpy dtype

Expand source code
def numpy_dtype(self):
    """Return numpy dtype"""
    return self.dtype().numpy_dtype()
def shape(self) ‑> tuple

tensor shape

Expand source code
def shape(self) -> tuple:
    """tensor shape"""
    dims = []
    for i in range(self.ndim()):
        dims.append(self.dim(i))

    return tuple(dims)
def size(self) ‑> int

Size in bytes

Expand source code
def size(self) -> int:
    """Size in bytes"""
    return LIBNUX.nux_tensor_size(self)