def test_convex_hull(self): # Simple check that the convex hull seems to works points = np.array([(0, 0), (0, 1), (1, 1), (1, 0)], dtype=np.double) tri = qhull.Delaunay(points) # +---+ # |\ 0| # | \ | # |1 \| # +---+ assert_equal(tri.convex_hull, [[3, 2], [1, 2], [1, 0], [3, 0]])
def test_regression_2359(self): # Check regression --- for certain point sets, gradient # estimation could end up in an infinite loop points = np.load(data_file('estimate_gradients_hang.npy')) values = np.random.rand(points.shape[0]) tri = qhull.Delaunay(points) # This should not hang with warnings.catch_warnings(): warnings.simplefilter('ignore', category=interpnd.GradientEstimationWarning) interpnd.estimate_gradients_2d_global(tri, values, maxiter=1)
def test_tripoints_input_rescale(self): # Test at single points x = np.array([(0,0), (-5,-5), (-5,5), (5, 5), (2.5, 3)], dtype=np.double) y = np.arange(x.shape[0], dtype=np.double) y = y - 3j*y tri = qhull.Delaunay(x) yi = interpnd.LinearNDInterpolator(tri.points, y)(x) yi_rescale = interpnd.LinearNDInterpolator(tri.points, y, rescale=True)(x) assert_almost_equal(yi, yi_rescale)
def test_regression_2359(self): # Check regression --- for certain point sets, gradient # estimation could end up in an infinite loop points = np.load(data_file('estimate_gradients_hang.npy')) values = np.random.rand(points.shape[0]) tri = qhull.Delaunay(points) # This should not hang with suppress_warnings() as sup: sup.filter(interpnd.GradientEstimationWarning, "Gradient estimation did not converge") interpnd.estimate_gradients_2d_global(tri, values, maxiter=1)
def test_tri_input_rescale(self): # Test at single points x = np.array([(0, 0), (-5, -5), (-5, 5), (5, 5), (2.5, 3)], dtype=np.double) y = np.arange(x.shape[0], dtype=np.double) y = y - 3j * y tri = qhull.Delaunay(x) match = ("Rescaling is not supported when passing a " "Delaunay triangulation as ``points``.") with pytest.raises(ValueError, match=match): interpnd.CloughTocher2DInterpolator(tri, y, rescale=True)(x)
def interp_weights(xyz, uvw): """The initial part of griddata (using linear interpolation) - Creates a Delaunay mesh triangulation of the source points, each point in the mesh is transformed to the new mesh using an interpolation of each point inside a triangle is done using barycentric coordinates""" tri = qhull.Delaunay(xyz) simplex = tri.find_simplex(uvw) vertices = np.take(tri.simplices, simplex, axis=0) temp = np.take(tri.transform, simplex, axis=0) delta = uvw - temp[:, d] bary = np.einsum('njk,nk->nj', temp[:, :d, :], delta) return vertices, np.hstack((bary, 1 - bary.sum(axis=1, keepdims=True)))
def check(name, chunksize): points = DATASETS[name] ndim = points.shape[1] opts = None nmin = ndim + 2 if name == 'some-points': # since Qz is not allowed, use QJ opts = 'QJ Pp' elif name == 'pathological-1': # include enough points so that we get different x-coordinates nmin = 12 obj = qhull.Delaunay(points[:nmin], incremental=True, qhull_options=opts) for j in xrange(nmin, len(points), chunksize): obj.add_points(points[j:j+chunksize]) obj2 = qhull.Delaunay(points) obj3 = qhull.Delaunay(points[:nmin], incremental=True, qhull_options=opts) obj3.add_points(points[nmin:], restart=True) # Check that the incremental mode agrees with upfront mode if name.startswith('pathological'): # XXX: These produce valid but different triangulations. # They look OK when plotted, but how to check them? assert_array_equal(np.unique(obj.simplices.ravel()), np.arange(points.shape[0])) assert_array_equal(np.unique(obj2.simplices.ravel()), np.arange(points.shape[0])) else: assert_unordered_tuple_list_equal(obj.simplices, obj2.simplices, tpl=sorted_tuple) assert_unordered_tuple_list_equal(obj2.simplices, obj3.simplices, tpl=sorted_tuple)
def check(name): points = DATASETS[name] tri = qhull.Delaunay(points) hull = qhull.ConvexHull(points) assert_hulls_equal(points, tri.convex_hull, hull.simplices) # Check that the hull extremes are as expected if points.shape[1] == 2: assert_equal(np.unique(hull.simplices), np.sort(hull.vertices)) else: assert_equal(np.unique(hull.simplices), hull.vertices)
def interp_weights(xy, uv,d=2): '''calculate triangulation and coordinates transformation using qhull.Delaunay 1) Triangulate the irregular grid coordinates xy; 2) For each point in the new grid uv, search which simplex does it lay 3) Calculate barycentric coordinates with respect to the vertices of enclosing simplex ''' tri = qhull.Delaunay(xy) simplex = tri.find_simplex(uv) vertices = np.take(tri.simplices, simplex, axis=0) temp = np.take(tri.transform, simplex, axis=0) delta = uv - temp[:, d] bary = np.einsum('njk,nk->nj', temp[:, :d, :], delta) return vertices, np.hstack((bary, 1 - bary.sum(axis=1, keepdims=True)))
def interp_weights_nd(xyz, uvw, d=2): # Check for right shape: if xyz.shape[1] != d: xyz = xyz.T if uvw.shape[1] != d: uvw = uvw.T tri = qhull.Delaunay(xyz) simplex = tri.find_simplex(uvw) vertices = np.take(tri.simplices, simplex, axis=0) temp = np.take(tri.transform, simplex, axis=0) delta = uvw - temp[:, d] bary = np.einsum('njk,nk->nj', temp[:, :d, :], delta) return vertices, np.hstack((bary, 1 - bary.sum(axis=1, keepdims=True)))
def test_coplanar(self): # Check that the coplanar point output option indeed works points = np.random.rand(10, 2) points = np.r_[points, points] # duplicate input data tri = qhull.Delaunay(points) assert_(len(np.unique(tri.simplices.ravel())) == len(points) // 2) assert_(len(tri.coplanar) == len(points) // 2) assert_(len(np.unique(tri.coplanar[:, 2])) == len(points) // 2) assert_(np.all(tri.vertex_to_simplex >= 0))
def computeV(self,values): """ Compute rotation matrix _V, and triangulation self.tri :param values: Nested array with the data values """ if not self._V is None: return Morig= [self.flattenArray(pt[0]) for pt in values] aM = np.array(Morig) MT = aM.T.tolist() self.delta_x = np.array([[ sum(x)/len(Morig) for x in MT ]]) M = [] for Mx in Morig: m=(np.array([Mx]) - self.delta_x).tolist()[0] M.append(m) try: ## we dont need thousands of points for SVD n = int(math.ceil(len(M)/2000.)) Vt=svd(M[::n])[2] except Exception as e: raise SModelSError("exception caught when performing singular value decomposition: %s, %s" %(type(e), e)) V=Vt.T self._V= V ## self.round ( V ) Mp=[] ## the dimensionality of the whole mass space, disrespecting equal branches ## assumption self.full_dimensionality = len(Morig[0]) self.dimensionality=0 for m in M: mp=np.dot(m,V) Mp.append ( mp ) nz=self.countNonZeros(mp) if nz>self.dimensionality: self.dimensionality=nz MpCut=[] for i in Mp: MpCut.append(i[:self.dimensionality].tolist() ) if self.dimensionality > 1: self.tri = qhull.Delaunay(MpCut) else: self.tri = Delaunay1D(MpCut)
def test_more_barycentric_transforms(self): # Triangulate some "nasty" grids eps = np.finfo(float).eps npoints = {2: 70, 3: 11, 4: 5, 5: 3} for ndim in xrange(2, 6): # Generate an uniform grid in n-d unit cube x = np.linspace(0, 1, npoints[ndim]) grid = np.c_[list( map(np.ravel, np.broadcast_arrays(*np.ix_(*([x] * ndim)))))].T err_msg = "ndim=%d" % ndim # Check using regular grid tri = qhull.Delaunay(grid) self._check_barycentric_transforms(tri, err_msg=err_msg, unit_cube=True) # Check with eps-perturbations np.random.seed(1234) m = (np.random.rand(grid.shape[0]) < 0.2) grid[m, :] += 2 * eps * (np.random.rand(*grid[m, :].shape) - 0.5) tri = qhull.Delaunay(grid) self._check_barycentric_transforms(tri, err_msg=err_msg, unit_cube=True, unit_cube_tol=2 * eps) # Check with duplicated data tri = qhull.Delaunay(np.r_[grid, grid]) self._check_barycentric_transforms(tri, err_msg=err_msg, unit_cube=True, unit_cube_tol=2 * eps)
def _get_subset_nodes(self, grid_x, grid_y, target_x, target_y): """ Retuns grid nodes that are necessary for intepolating onto target_x,y """ orig_shape = grid_x.shape grid_xy = np.array((grid_x.ravel(), grid_y.ravel())).T target_xy = np.array((target_x.ravel(), target_y.ravel())).T tri = qhull.Delaunay(grid_xy) simplex = tri.find_simplex(target_xy) vertices = np.take(tri.simplices, simplex, axis=0) nodes = np.unique(vertices.ravel()) nodes_x, nodes_y = np.unravel_index(nodes, orig_shape) return nodes, nodes_x, nodes_y
def test_nd_simplex(self): # simple smoke test: triangulate a n-dimensional simplex for nd in xrange(2, 8): points = np.zeros((nd+1, nd)) for j in xrange(nd): points[j,j] = 1.0 points[-1,:] = 1.0 tri = qhull.Delaunay(points) tri.vertices.sort() assert_equal(tri.vertices, np.arange(nd+1, dtype=np.int)[None,:]) assert_equal(tri.neighbors, -1 + np.zeros((nd+1), dtype=np.int)[None,:])
def _interp_weights(xyz, uvw, d=None): """ :param xyz: flattened coords of current grid :param uvw: flattened coords of target grid :param d: number of dimensions of new grid :return: triangulisation lookup table, point weights """ tri = qhull.Delaunay(xyz) simplex = tri.find_simplex(uvw) vertices = np.take(tri.simplices, simplex, axis=0) temp = np.take(tri.transform, simplex, axis=0) delta = uvw - temp[:, d] bary = np.einsum('njk,nk->nj', temp[:, :d, :], delta) return vertices, np.hstack((bary, 1 - bary.sum(axis=1, keepdims=True)))
def interp_weights(xy, uv, d=2): # pragma: no cover tri = qhull.Delaunay(xy) simplex = tri.find_simplex(uv) vertices = np.take(tri.simplices, simplex, axis=0) temp = np.take(tri.transform, simplex, axis=0) delta = uv - temp[:, d] bary = np.einsum('njk,nk->nj', temp[:, :d, :], delta) return vertices, np.hstack((bary, 1 - bary.sum(axis=1, keepdims=True)))
def check(name): chunks, opts = INCREMENTAL_DATASETS[name] points = np.concatenate(chunks, axis=0) obj = qhull.Delaunay(chunks[0], incremental=True, qhull_options=opts) for chunk in chunks[1:]: obj.add_points(chunk) obj2 = qhull.Delaunay(points) obj3 = qhull.Delaunay(chunks[0], incremental=True, qhull_options=opts) if len(chunks) > 1: obj3.add_points(np.concatenate(chunks[1:], axis=0), restart=True) # Check that the incremental mode agrees with upfront mode if name.startswith('pathological'): # XXX: These produce valid but different triangulations. # They look OK when plotted, but how to check them? assert_array_equal(np.unique(obj.simplices.ravel()), np.arange(points.shape[0])) assert_array_equal(np.unique(obj2.simplices.ravel()), np.arange(points.shape[0])) else: assert_unordered_tuple_list_equal(obj.simplices, obj2.simplices, tpl=sorted_tuple) assert_unordered_tuple_list_equal(obj2.simplices, obj3.simplices, tpl=sorted_tuple)
def test_hull_consistency_tri(self, name): # Check that a convex hull returned by qhull in ndim # and the hull constructed from ndim delaunay agree points = DATASETS[name] tri = qhull.Delaunay(points) hull = qhull.ConvexHull(points) assert_hulls_equal(points, tri.convex_hull, hull.simplices) # Check that the hull extremes are as expected if points.shape[1] == 2: assert_equal(np.unique(hull.simplices), np.sort(hull.vertices)) else: assert_equal(np.unique(hull.simplices), hull.vertices)
def test_triangle(self): points = np.array([(0,0), (0,1), (1,0)], dtype=np.double) tri = qhull.Delaunay(points) # 1 # + # |\ # | \ # |0 \ # +---+ # 0 2 self._check_ridges(tri, 0, [(0, 1), (0, 2)]) self._check_ridges(tri, 1, [(1, 0), (1, 2)]) self._check_ridges(tri, 2, [(2, 0), (2, 1)])
def __init__(self, grid_xyz, target_xyz, fill_mode=None, fill_value=np.nan, normalize=False): """ :arg grid_xyz: Array of source grid coordinates, shape (npoints, 2) or (npoints, 3) :arg target_xyz: Array of target grid coordinates, shape (n, 2) or (n, 3) """ self.fill_value = np.nan self.fill_mode = fill_mode self.normalize = normalize if self.normalize: def get_norm_params(x, scale=None): min = x.min() max = x.max() if scale is None: scale = max - min a = 1./scale b = -min*a return a, b ax, bx = get_norm_params(target_xyz[:, 0]) ay, by = get_norm_params(target_xyz[:, 1]) az, bz = get_norm_params(target_xyz[:, 2]) self.norm_a = np.array([ax, ay, az]) self.norm_b = np.array([bx, by, bz]) ngrid_xyz = self.norm_a*grid_xyz + self.norm_b ntarget_xyz = self.norm_a*target_xyz + self.norm_b else: ngrid_xyz = grid_xyz ntarget_xyz = target_xyz d = ngrid_xyz.shape[1] tri = qhull.Delaunay(ngrid_xyz) # NOTE this becomes expensive in 3D for npoints > 10k simplex = tri.find_simplex(ntarget_xyz) vertices = np.take(tri.simplices, simplex, axis=0) temp = np.take(tri.transform, simplex, axis=0) delta = ntarget_xyz - temp[:, d] bary = np.einsum('njk,nk->nj', temp[:, :d, :], delta) self.vtx = vertices self.wts = np.hstack((bary, 1 - bary.sum(axis=1, keepdims=True))) self.outside = np.nonzero(np.any(self.wts < 0, axis=1))[0] self.fill_nearest = self.fill_mode == 'nearest' and len(self.outside) > 0 if self.fill_nearest: # find nearest neighbor in the data set from scipy.spatial import cKDTree dist, ix = cKDTree(ngrid_xyz).query(ntarget_xyz[self.outside]) self.outside_to_nearest = ix
def test_tri_input_rescale(self): # Test at single points x = np.array([(0, 0), (-5, -5), (-5, 5), (5, 5), (2.5, 3)], dtype=np.double) y = np.arange(x.shape[0], dtype=np.double) y = y - 3j * y tri = qhull.Delaunay(x) try: interpnd.CloughTocher2DInterpolator(tri, y, rescale=True)(x) except ValueError as a: if str(a) != ("Rescaling is not supported when passing a " "Delaunay triangulation as ``points``."): raise except: raise
def test_rectangle(self): points = np.array([(0,0), (0,1), (1,1), (1,0)], dtype=np.double) tri = qhull.Delaunay(points) # 1 2 # +---+ # |\ 0| # | \ | # |1 \| # +---+ # 0 3 self._check_ridges(tri, 0, [(0, 1), (0, 3)]) self._check_ridges(tri, 1, [(1, 0), (1, 3), (1, 2)]) self._check_ridges(tri, 2, [(2, 1), (2, 3)]) self._check_ridges(tri, 3, [(3, 0), (3, 1), (3, 2)])
def test_degenerate_barycentric_transforms(self): # The triangulation should not produce invalid barycentric # transforms that stump the simplex finding data = np.load(os.path.join(os.path.dirname(__file__), 'data', 'degenerate_pointset.npz')) points = data['c'] data.close() tri = qhull.Delaunay(points) # Check that there are not too many invalid simplices bad_count = np.isnan(tri.transform[:,0,0]).sum() assert_(bad_count < 20, bad_count) # Check the transforms self._check_barycentric_transforms(tri)
def get_interp_weights(points, xi, fill_value=np.nan): ''' Compute vertices and weights for 3D linear interpolation. Arguments --------- points : ndarray Coordinates of known points, shape = (N, 3). xi : ndarray Coordinates of points to be interpolated, shape = (M, 3). Keyword Arguments ----------------- fill_value : scalar Value used in output `weights` array for invalid points (outside convex hull). Returns ------- vertices : ndarray Shape = (M, 4). weights : ndarray Shape (M, 4). Notes ----- Adapted from https://stackoverflow.com/questions/20915502/ . ''' # Only 3d interpolation for now. d = 3 if points.shape[1] != d or xi.shape[1] != d: raise ValueError('Input shape not 3d.') T = qhull.Delaunay(points) simplex = T.find_simplex(xi) # Same shape as xi. Points outside get -1. vertices = np.take(T.simplices, simplex, axis=0) temp = np.take(T.transform, simplex, axis=0) delta = xi - temp[:, d] bary = np.einsum('njk,nk->nj', temp[:, :d, :], delta) weights = np.hstack((bary, 1 - bary.sum(axis=1, keepdims=True))) weights[simplex == -1] *= fill_value return vertices, weights
def test_find_simplex(self): # Simple check that simplex finding works points = np.array([(0, 0), (0, 1), (1, 1), (1, 0)], dtype=np.double) tri = qhull.Delaunay(points) # +---+ # |\ 0| # | \ | # |1 \| # +---+ assert_equal(tri.vertices, [[1, 3, 2], [3, 1, 0]]) for p in [(0.25, 0.25, 1), (0.75, 0.75, 0), (0.3, 0.2, 1)]: i = tri.find_simplex(p[:2]) assert_equal(i, p[2], err_msg='%r' % (p, )) j = qhull.tsearch(tri, p[:2]) assert_equal(i, j)
def _get_subset_nodes(grid_x, grid_y, target_x, target_y): """ Retuns grid nodes that are necessary for intepolating onto target_x,y """ orig_shape = grid_x.shape grid_xy = np.array((grid_x.ravel(), grid_y.ravel())).T target_xy = np.array((target_x.ravel(), target_y.ravel())).T tri = qhull.Delaunay(grid_xy) simplex = tri.find_simplex(target_xy) vertices = np.take(tri.simplices, simplex, axis=0) nodes = np.unique(vertices.ravel()) nodes_x, nodes_y = np.unravel_index(nodes, orig_shape) # x and y bounds for reading a subset of the netcdf data ind_x = slice(nodes_x.min(), nodes_x.max() + 1) ind_y = slice(nodes_y.min(), nodes_y.max() + 1) return nodes, ind_x, ind_y
def generate_triangulation(self): xc = self.x.ravel() yc = self.y.ravel() zc = self.z.ravel() # Remove any NaN values as the triangulation can't handle this nans = np.isnan(zc) xc = xc[~nans] yc = yc[~nans] self.no_nan_values = zc[~nans] # Normalize the coordinates. This improves the triangulation results # in cases where the data ranges on both axes are very different # in magnitude xmin, xmax, ymin, ymax, _, _ = self.get_limits() xc = (xc - xmin) / (xmax - xmin) yc = (yc - ymin) / (ymax - ymin) self.tri = qhull.Delaunay(np.column_stack((xc, yc)))
def interp_weights(xy, uv, d=2): """Compute the nearest vertices & weights (for reuse)""" tri = qhull.Delaunay(xy) simplex = tri.find_simplex(uv) if not np.all(simplex >= 0): if not self.params.mesh.periodic_bc: log.error("Some points missing in interpolation " "of velocity obs to function space.") else: log.warning("Some points missing in interpolation " "of velocity obs to function space.") vertices = np.take(tri.simplices, simplex, axis=0) temp = np.take(tri.transform, simplex, axis=0) delta = uv - temp[:, d] bary = np.einsum('njk,nk->nj', temp[:, :d, :], delta) return vertices, np.hstack( (bary, 1 - bary.sum(axis=1, keepdims=True)))
def triangulate(xy): """ triangulate(xy) Compute the D-D Delaunay triangulation of a grid. Parameters ---------- xy : 2-D ndarray of floats with shape (n, D), or length D tuple of 1-D ndarrays with shape (n,). Data point coordinates. Returns ------- tri : Delaunay object 2D Delaunay triangulation of `xy`. """ tri = qhull.Delaunay(xy) return tri