Module calib3d.points
Working with homogenous coordinates
The vector used to represent 2D and 3D points are vertical vectors, which are stored as 2D matrices in numpy.
Furthemore, in homogenous coordinates: a 3D point (x,y,z) in the world is represented by a 4 element vector
(𝜆x,𝜆y,𝜆z,𝜆) where 𝜆 ∈ ℝ₀.
To simplify access to x and y (and z) coordinates of those points as well as computations in homogenous coordinates,
we defined the types Point2D (and Point3D)
extending numpy.ndarray. Therefore, access to y coordinate of point is point.y instead of
point[1][0] (point[1][:] for an array of points), and access to homogenous coordinates is made easy with point.H,
while it is still possible to use point with any numpy operators.
Expand source code
from abc import ABCMeta, abstractproperty
import warnings
import numpy as np
__doc__ = r"""
# Working with homogenous coordinates
The vector used to represent 2D and 3D points are vertical vectors, which are stored as 2D matrices in `numpy`.
Furthemore, in homogenous coordinates: a 3D point (x,y,z) in the world is represented by a 4 element vector
(𝜆x,𝜆y,𝜆z,𝜆) where 𝜆 ∈ ℝ₀.
To simplify access to x and y (and z) coordinates of those points as well as computations in homogenous coordinates,
we defined the types [`Point2D`](#Point2D) (and [`Point3D`](#Point3D))
extending `numpy.ndarray`. Therefore, access to y coordinate of point is `point.y` instead of
`point[1][0]` (`point[1][:]` for an array of points), and access to homogenous coordinates is made easy with `point.H`,
while it is still possible to use point with any numpy operators.
"""
class HomogeneousCoordinatesPoint(np.ndarray, metaclass=ABCMeta):
""" Extension of Numpy `np.ndarray` that implements generic homogenous coordinates points
for `Point2D` and `Point3D` objects. The constructor supports multiple formats for creation
of a single point or array of multiple points.
Example with creation of `Point2D` objects, all formulations are equivalent:
```
>>> x, y = 1, 2
>>> Point2D(x, y)
>>> Point2D(4*x, 4*y, 4)
>>> Point2D(np.array([[x], [y]]))
>>> Point2D(4*np.array([[x], [y], [1]]))
Point2D([[1.],
[2.]])
```
"""
def __new__(cls, *coords):
if len(coords) == 1:
if isinstance(coords, list):
coords = np.hstack(coords)
else:
coords = coords[0]
array = np.array(coords) if isinstance(coords, (tuple, list)) else coords
invalid_shape_message = "Invalid input shape:\n" \
"Expected a 2D np.array of shape ({l1},N) or (N,{l1},1) in non-homogenous coordinates\n" \
" or shape ({l2},N) or (N,{l2},1) in homogenous coordinates\n" \
"Received a np.array of shape {shape}".format(l1=cls.D, l2=cls.D+1, shape=array.shape)
if len(array.shape) == 1:
array = array[:, np.newaxis]
elif len(array.shape) == 2:
pass
elif len(array.shape) == 3:
array = array[..., 0].T
else:
raise ValueError(invalid_shape_message)
if array.shape[0] == cls.D: # point(s) given in non-homogenous coordinates
pass
elif array.shape[0] == cls.D+1: # point(s) given in homogenous coordinates
array = array[0:cls.D,:]/array[cls.D,:]
elif array.shape[0] == 0: # point given from an empty list should be an empty point
array = np.empty((cls.D,0))
else:
raise ValueError(invalid_shape_message)
return array.astype(np.float64).view(cls)
# def __array_ufunc__():
# TODO
# def __array_wrap__(self, out_arr, context=None):
# return super().__array_wrap__(self, out_arr, context)
@property
@abstractproperty
def _coord_names(self):
raise NotImplementedError
x = property(fget=lambda self: self._get_coord(0), fset=lambda self, value: self._set_coord(0, value), doc="Point's x component")
y = property(fget=lambda self: self._get_coord(1), fset=lambda self, value: self._set_coord(1, value), doc="Point's y component")
z = property(fget=lambda self: self._get_coord(2), fset=lambda self, value: self._set_coord(2, value), doc="Point's z component (only valid for `Point3D` objects)")
@property
def H(self):
""" Point expressed in homogenous coordinates with an homogenous component equal to `1`.
Example:
```
>>> p = Point3D(1,2,3)
>>> p.H
array([[1.],
[2.],
[3.],
[1.]])
```
"""
return np.vstack((self, np.ones((1, self.shape[1]))))
# @property
# def D(self):
# """ Returns the number of spacial dimensions """
# return len(self._coord_names)
# /!\ a different getitem gives too much trouble with all numpy operator
#def __getitem__(self, i):
# if isinstance(i, int):
# return self.__class__(super().__getitem__((slice(None), i)))
# return super().__getitem__(i)
# /!\ iter may conflict with numpy array getitem.
def __iter__(self):
return (self.__class__(self[:,i:i+1]) for i in range(self.shape[1]))
def to_list(self):
""" Transforms a single point to a python list.
Raises:
AssertionError if the object is an array of multiple points
"""
assert self.shape[1] == 1, "to_list() method can only be used on single point {}".format(self.__class__.__name__)
return self[:,0].flatten().tolist()
def flatten(self):
""" Flatten the points.
.. todo:: integrate this in the __array_ufunc__ to prevent type forwarding
"""
return np.asarray(super().flatten())
def to_int_tuple(self):
""" Transforms a single point to a python tuple with integer coordinates
"""
return tuple(int(x) for x in self.to_list())
def linspace(self, num):
""" Linearly interpolate points in `num-1` intervals.
Example:
```
>>> Point2D([0,4,4],[0,0,4]).linspace(5)
[[0. 1. 2. 3. 4. 4. 4. 4. 4. 4.]
[0. 0. 0. 0. 0. 0. 1. 2. 3. 4.]]
```
"""
return np.transpose(np.linspace(self[:,:-1], self[:,1:], num), axes=(1,2,0)).reshape(len(self._coord_names),-1)
def close(self):
""" Copy the first point in an array of points and place it at the end of that array,
hence "closing" the polygon defined by the initial points.
TODO: add Points2D and Points3D classes
"""
assert self.shape[1] > 1, f"Invalid use of 'close' method: points' shape '{self.shape}' expected to be > 1 in the second dimension"
return self.__class__(np.hstack((self, self[:,0:1])))
_get_coord = lambda self, i: np.asarray(super().__getitem__((i,0))) if self.shape[1] == 1 else np.asarray(super().__getitem__(i))
_set_coord = lambda self, i, value: super().__setitem__((i,0), value) if self.shape[1] == 1 else super().__setitem__((i), value)
class Point2D(HomogeneousCoordinatesPoint):
""" Numpy representation of a single 2D point or a list of 2D points
"""
D = 2
_coord_names = ("x","y")
class Point3D(HomogeneousCoordinatesPoint):
""" Numpy representation of a single 3D point or a list of 3D points
"""
D = 3
_coord_names = ("x","y","z")
@property
def V(self):
array = self.H
array[-1] = 0
return VanishingPoint(array)
class VanishingPoint(Point3D):
""" Object allowing representation of Vanishing point (with null homogenous
coordinate). Only the `H` attribute should be used. Handle with care.
"""
def __new__(cls, array):
warnings.warn("Vanishing Point feature has not yet been fully tested")
obj = array.astype(np.float64).view(cls)
obj.array = array
return obj
@property
def H(self):
return self.array
def __getattribute__(self, attr_name):
if attr_name not in ("H", "__array_finalize__", "array", "shape", "size", "ndim", "x", "y", "z", "_get_coord", "close", "__class__", "astype", "view"):
raise AttributeError(f"VanishingPoint has no `{attr_name}` attribute.")
return super().__getattribute__(attr_name)
Classes
class HomogeneousCoordinatesPoint (*coords)-
Extension of Numpy
np.ndarraythat implements generic homogenous coordinates points forPoint2DandPoint3Dobjects. The constructor supports multiple formats for creation of a single point or array of multiple points.Example with creation of
Point2Dobjects, all formulations are equivalent:>>> x, y = 1, 2 >>> Point2D(x, y) >>> Point2D(4*x, 4*y, 4) >>> Point2D(np.array([[x], [y]])) >>> Point2D(4*np.array([[x], [y], [1]])) Point2D([[1.], [2.]])Expand source code
class HomogeneousCoordinatesPoint(np.ndarray, metaclass=ABCMeta): """ Extension of Numpy `np.ndarray` that implements generic homogenous coordinates points for `Point2D` and `Point3D` objects. The constructor supports multiple formats for creation of a single point or array of multiple points. Example with creation of `Point2D` objects, all formulations are equivalent: ``` >>> x, y = 1, 2 >>> Point2D(x, y) >>> Point2D(4*x, 4*y, 4) >>> Point2D(np.array([[x], [y]])) >>> Point2D(4*np.array([[x], [y], [1]])) Point2D([[1.], [2.]]) ``` """ def __new__(cls, *coords): if len(coords) == 1: if isinstance(coords, list): coords = np.hstack(coords) else: coords = coords[0] array = np.array(coords) if isinstance(coords, (tuple, list)) else coords invalid_shape_message = "Invalid input shape:\n" \ "Expected a 2D np.array of shape ({l1},N) or (N,{l1},1) in non-homogenous coordinates\n" \ " or shape ({l2},N) or (N,{l2},1) in homogenous coordinates\n" \ "Received a np.array of shape {shape}".format(l1=cls.D, l2=cls.D+1, shape=array.shape) if len(array.shape) == 1: array = array[:, np.newaxis] elif len(array.shape) == 2: pass elif len(array.shape) == 3: array = array[..., 0].T else: raise ValueError(invalid_shape_message) if array.shape[0] == cls.D: # point(s) given in non-homogenous coordinates pass elif array.shape[0] == cls.D+1: # point(s) given in homogenous coordinates array = array[0:cls.D,:]/array[cls.D,:] elif array.shape[0] == 0: # point given from an empty list should be an empty point array = np.empty((cls.D,0)) else: raise ValueError(invalid_shape_message) return array.astype(np.float64).view(cls) # def __array_ufunc__(): # TODO # def __array_wrap__(self, out_arr, context=None): # return super().__array_wrap__(self, out_arr, context) @property @abstractproperty def _coord_names(self): raise NotImplementedError x = property(fget=lambda self: self._get_coord(0), fset=lambda self, value: self._set_coord(0, value), doc="Point's x component") y = property(fget=lambda self: self._get_coord(1), fset=lambda self, value: self._set_coord(1, value), doc="Point's y component") z = property(fget=lambda self: self._get_coord(2), fset=lambda self, value: self._set_coord(2, value), doc="Point's z component (only valid for `Point3D` objects)") @property def H(self): """ Point expressed in homogenous coordinates with an homogenous component equal to `1`. Example: ``` >>> p = Point3D(1,2,3) >>> p.H array([[1.], [2.], [3.], [1.]]) ``` """ return np.vstack((self, np.ones((1, self.shape[1])))) # @property # def D(self): # """ Returns the number of spacial dimensions """ # return len(self._coord_names) # /!\ a different getitem gives too much trouble with all numpy operator #def __getitem__(self, i): # if isinstance(i, int): # return self.__class__(super().__getitem__((slice(None), i))) # return super().__getitem__(i) # /!\ iter may conflict with numpy array getitem. def __iter__(self): return (self.__class__(self[:,i:i+1]) for i in range(self.shape[1])) def to_list(self): """ Transforms a single point to a python list. Raises: AssertionError if the object is an array of multiple points """ assert self.shape[1] == 1, "to_list() method can only be used on single point {}".format(self.__class__.__name__) return self[:,0].flatten().tolist() def flatten(self): """ Flatten the points. .. todo:: integrate this in the __array_ufunc__ to prevent type forwarding """ return np.asarray(super().flatten()) def to_int_tuple(self): """ Transforms a single point to a python tuple with integer coordinates """ return tuple(int(x) for x in self.to_list()) def linspace(self, num): """ Linearly interpolate points in `num-1` intervals. Example: ``` >>> Point2D([0,4,4],[0,0,4]).linspace(5) [[0. 1. 2. 3. 4. 4. 4. 4. 4. 4.] [0. 0. 0. 0. 0. 0. 1. 2. 3. 4.]] ``` """ return np.transpose(np.linspace(self[:,:-1], self[:,1:], num), axes=(1,2,0)).reshape(len(self._coord_names),-1) def close(self): """ Copy the first point in an array of points and place it at the end of that array, hence "closing" the polygon defined by the initial points. TODO: add Points2D and Points3D classes """ assert self.shape[1] > 1, f"Invalid use of 'close' method: points' shape '{self.shape}' expected to be > 1 in the second dimension" return self.__class__(np.hstack((self, self[:,0:1]))) _get_coord = lambda self, i: np.asarray(super().__getitem__((i,0))) if self.shape[1] == 1 else np.asarray(super().__getitem__(i)) _set_coord = lambda self, i, value: super().__setitem__((i,0), value) if self.shape[1] == 1 else super().__setitem__((i), value)Ancestors
- numpy.ndarray
Subclasses
Instance variables
var H-
Point expressed in homogenous coordinates with an homogenous component equal to
1.Example:
>>> p = Point3D(1,2,3) >>> p.H array([[1.], [2.], [3.], [1.]])Expand source code
@property def H(self): """ Point expressed in homogenous coordinates with an homogenous component equal to `1`. Example: ``` >>> p = Point3D(1,2,3) >>> p.H array([[1.], [2.], [3.], [1.]]) ``` """ return np.vstack((self, np.ones((1, self.shape[1])))) var x-
Point's x component
Expand source code
x = property(fget=lambda self: self._get_coord(0), fset=lambda self, value: self._set_coord(0, value), doc="Point's x component") var y-
Point's y component
Expand source code
y = property(fget=lambda self: self._get_coord(1), fset=lambda self, value: self._set_coord(1, value), doc="Point's y component") var z-
Point's z component (only valid for
Point3Dobjects)Expand source code
z = property(fget=lambda self: self._get_coord(2), fset=lambda self, value: self._set_coord(2, value), doc="Point's z component (only valid for `Point3D` objects)")
Methods
def close(self)-
Copy the first point in an array of points and place it at the end of that array, hence "closing" the polygon defined by the initial points.
TODO: add Points2D and Points3D classes
Expand source code
def close(self): """ Copy the first point in an array of points and place it at the end of that array, hence "closing" the polygon defined by the initial points. TODO: add Points2D and Points3D classes """ assert self.shape[1] > 1, f"Invalid use of 'close' method: points' shape '{self.shape}' expected to be > 1 in the second dimension" return self.__class__(np.hstack((self, self[:,0:1]))) def flatten(self)-
Flatten the points.
TODO
integrate this in the array_ufunc to prevent type forwarding
Expand source code
def flatten(self): """ Flatten the points. .. todo:: integrate this in the __array_ufunc__ to prevent type forwarding """ return np.asarray(super().flatten()) def linspace(self, num)-
Linearly interpolate points in
num-1intervals.Example:
>>> Point2D([0,4,4],[0,0,4]).linspace(5) [[0. 1. 2. 3. 4. 4. 4. 4. 4. 4.] [0. 0. 0. 0. 0. 0. 1. 2. 3. 4.]]Expand source code
def linspace(self, num): """ Linearly interpolate points in `num-1` intervals. Example: ``` >>> Point2D([0,4,4],[0,0,4]).linspace(5) [[0. 1. 2. 3. 4. 4. 4. 4. 4. 4.] [0. 0. 0. 0. 0. 0. 1. 2. 3. 4.]] ``` """ return np.transpose(np.linspace(self[:,:-1], self[:,1:], num), axes=(1,2,0)).reshape(len(self._coord_names),-1) def to_int_tuple(self)-
Transforms a single point to a python tuple with integer coordinates
Expand source code
def to_int_tuple(self): """ Transforms a single point to a python tuple with integer coordinates """ return tuple(int(x) for x in self.to_list()) def to_list(self)-
Transforms a single point to a python list.
Raises
AssertionError if the object is an array of multiple points
Expand source code
def to_list(self): """ Transforms a single point to a python list. Raises: AssertionError if the object is an array of multiple points """ assert self.shape[1] == 1, "to_list() method can only be used on single point {}".format(self.__class__.__name__) return self[:,0].flatten().tolist()
class Point2D (*coords)-
Numpy representation of a single 2D point or a list of 2D points
Expand source code
class Point2D(HomogeneousCoordinatesPoint): """ Numpy representation of a single 2D point or a list of 2D points """ D = 2 _coord_names = ("x","y")Ancestors
- HomogeneousCoordinatesPoint
- numpy.ndarray
Class variables
var D
Inherited members
class Point3D (*coords)-
Numpy representation of a single 3D point or a list of 3D points
Expand source code
class Point3D(HomogeneousCoordinatesPoint): """ Numpy representation of a single 3D point or a list of 3D points """ D = 3 _coord_names = ("x","y","z") @property def V(self): array = self.H array[-1] = 0 return VanishingPoint(array)Ancestors
- HomogeneousCoordinatesPoint
- numpy.ndarray
Subclasses
Class variables
var D
Instance variables
var V-
Expand source code
@property def V(self): array = self.H array[-1] = 0 return VanishingPoint(array)
Inherited members
class VanishingPoint (array)-
Object allowing representation of Vanishing point (with null homogenous coordinate). Only the
Hattribute should be used. Handle with care.Expand source code
class VanishingPoint(Point3D): """ Object allowing representation of Vanishing point (with null homogenous coordinate). Only the `H` attribute should be used. Handle with care. """ def __new__(cls, array): warnings.warn("Vanishing Point feature has not yet been fully tested") obj = array.astype(np.float64).view(cls) obj.array = array return obj @property def H(self): return self.array def __getattribute__(self, attr_name): if attr_name not in ("H", "__array_finalize__", "array", "shape", "size", "ndim", "x", "y", "z", "_get_coord", "close", "__class__", "astype", "view"): raise AttributeError(f"VanishingPoint has no `{attr_name}` attribute.") return super().__getattribute__(attr_name)Ancestors
- Point3D
- HomogeneousCoordinatesPoint
- numpy.ndarray
Inherited members