Source code for pyoints.extent

# BEGIN OF LICENSE NOTE
# This file is part of Pyoints.
# Copyright (c) 2018, Sebastian Lamprecht, Trier University,
# lamprecht@uni-trier.de
#
# Pyoints is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyoints is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Pyoints. If not, see <https://www.gnu.org/licenses/>.
# END OF LICENSE NOTE
"""Handles spatial extents.
"""

import numpy as np

from . import assertion

from . misc import print_rounded


[docs]class Extent(np.recarray, object): """Specifies spatial extent (or bounding box) of coordinates in `k` dimensions. Parameters ---------- ext : array_like(Number, shape=(2 * k)) or array_like(Number, shape=(n, k)) Defines spatial extent of `k` dimensions as either minimum corner and maximum corner or as a set of `n` points. If a set of points is given, the bounding box of these coordinates is calculated. Attributes ---------- dim : positive int Number of coordinate dimensions. ranges : np.ndarray(Number, shape=(self.dim)) Ranges between each coordinate dimension. min_corner,max_corner : array_like(Number, shape=(self.dim)) Minimum and maximum values in each coordinate dimension. center : array_like(Number, shape=(self.dim)) Focal point of the extent. Examples -------- Derive the extent of a list of points. >>> points = [(0, 0), (1, 4), (0, 1), (1, 0.5), (0.5, 0.7)] >>> ext = Extent(points) >>> print(ext) [ 0. 0. 1. 4.] Create a extent based on minimum and maximum values. >>> ext = Extent([-1, 0, 1, 4, ]) >>> print(ext) [-1 0 1 4] Get some properties. >>> print_rounded(ext.dim) 2 >>> print_rounded(ext.min_corner) [-1 0] >>> print_rounded(ext.max_corner) [1 4] >>> print_rounded(ext.ranges) [2 4] >>> print(ext.center) [ 0. 2.] >>> print_rounded(ext.corners) [[-1 0] [ 1 0] [ 1 4] [-1 4]] """ def __new__(cls, ext): ext = assertion.ensure_numarray(ext) if not len(ext.shape) <= 2: raise ValueError('vector or coordinates needed') if len(ext.shape) == 2: # coordinates min_ext = np.amin(ext, axis=0) max_ext = np.amax(ext, axis=0) ext = np.concatenate((min_ext, max_ext)) else: # vector dim = len(ext) // 2 if not dim * 2 == len(ext): raise ValueError('malformed extent vector') if not np.all(ext[:dim] <= ext[dim:]): raise ValueError('minima must not be greater than maxima') return ext.view(cls) @property def dim(self): return len(self) // 2 @property def ranges(self): return self.max_corner - self.min_corner @property def min_corner(self): return self[:self.dim] @property def max_corner(self): return self[self.dim:] @property def center(self): return (self.max_corner + self.min_corner) * 0.5
[docs] def split(self): """Splits the extent into the minimum and maximum corners. Returns ------- min_corner,max_corner : np.ndarray(Number, shape=(self.dim)) Minimum and maximum values in each coordinate dimension. """ return self.min_corner, self.max_corner
@property def corners(self): """Provides each corner of the extent box. Returns ------- corners : np.ndarray(Number, shape=(2\*\*self.dim, self.dim)) Corners of the extent. Examples -------- Two dimensional case. >>> ext = Extent([-1, -2, 1, 2]) >>> print_rounded(ext.corners) [[-1 -2] [ 1 -2] [ 1 2] [-1 2]] Three dimensional case. >>> ext = Extent([-1, -2, -3, 1, 2, 3]) >>> print_rounded(ext.corners) [[-1 -2 -3] [ 1 -2 -3] [ 1 2 -3] ..., [ 1 2 3] [ 1 -2 3] [-1 -2 3]] """ def combgen(dim): # generates order of corners if dim == 1: return np.array([[0, 1]], dtype=int).T else: comb = combgen(dim - 1) col = np.array([np.hstack(( np.zeros(len(comb)), np.ones(len(comb)), ))], dtype=int).T comb = np.vstack((comb, comb[::-1, :])) return np.hstack((comb, col)) combs = combgen(self.dim) combs = combs * self.dim + range(self.dim) return self[combs]
[docs] def intersection(self, coords, dim=None): """Tests if coordinates are located within the extent. Parameters ---------- coords : array_like(Number, shape=(n, k)) or array_like(Number, shape=(k)) Represents `n` data points of `k` dimensions. dim : positive int Desired number of dimensions to consider. Returns ------- indices : np.ndarray(int, shape=(n)) or np.ndarray(bool, shape=(n)) Indices of coordinates which are within the extent. If just a single point is given, a boolean value is returned. Examples -------- Point within extent? >>> ext = Extent([0, 0.5, 1, 4]) >>> print(ext.intersection([(0.5, 1)])) True Points within extent? >>> print_rounded(ext.intersection([(1, 2), (-1, 1), (0.5, 1)])) [0 2] Corners are located within the extent. >>> print_rounded(ext.intersection(ext.corners)) [0 1 2 3] """ # normalize data coords = assertion.ensure_numarray(coords) if len(coords.shape) == 1: coords = np.array([coords]) # set desired dimension dim = self.dim if dim is None else dim if not dim > 0: raise ValueError('dimension "dim" needs to be greater zero') # check n, c_dim = coords.shape if not c_dim <= self.dim: m = 'expected %i dimensions, but got %i' raise ValueError(m % (self.dim, c_dim)) min_ext, max_ext = self.split() # Order axes by range to speed up the process (heuristic) order = np.argsort(self.ranges[0:dim]) mask = np.any( (np.abs(min_ext[order]) < np.inf, np.abs(max_ext[order]) < np.inf), axis=0 ) axes = order[mask] indices = np.arange(n) for axis in axes: values = coords[indices, axis] # Minimum mask = values >= min_ext[axis] indices = indices[mask] if len(indices) == 0: break values = values[mask] # Maximum mask = values <= max_ext[axis] indices = indices[mask] if len(indices) == 0: break values = values[mask] if n == 1: return len(indices) == 1 return indices