def fit(src, dst, order=2): """function to fit this transform given the corresponding sets of points src & dst polynomial fit Parameters ---------- src : numpy.array a Nx2 matrix of source points dst : numpy.array a Nx2 matrix of destination points order : bool order of polynomial to fit Returns ------- numpy.array a [2,(order+1)*(order+2)/2] array with the best fit parameters """ xs = src[:, 0] ys = src[:, 1] xd = dst[:, 0] yd = dst[:, 1] rows = src.shape[0] no_coeff = (order + 1) * (order + 2) if len(src) != len(dst): raise EstimationError( 'source has {} points, but dest has {}!'.format( len(src), len(dst))) if no_coeff > len(src): raise EstimationError( 'order {} is too large to fit {} points!'.format( order, len(src))) A = np.zeros([rows * 2, no_coeff + 1]) pidx = 0 for j in range(order + 1): for i in range(j + 1): A[:rows, pidx] = xs**(j - i) * ys**i A[rows:, pidx + no_coeff // 2] = xs**(j - i) * ys**i pidx += 1 A[:rows, -1] = xd A[rows:, -1] = yd # right singular vector corresponding to smallest singular value _, s, V = svd(A) Vsm = V[np.argmin(s), :] # never trust computers return (-Vsm[:-1] / Vsm[-1]).reshape((2, no_coeff // 2))
def gradient_descent(self, pt, gamma=1.0, precision=0.0001, max_iters=1000): """based on https://en.wikipedia.org/wiki/Gradient_descent#Python Parameters ---------- pt : numpy array [x,y] point for estimating inverse gamma : float step size is gamma fraction of current gradient precision : float criteria for stopping for differences between steps max_iters : int limit for iterations, error if reached Returns ------- cur_pt : numpy array [x,y] point, estimated inverse of pt """ cur_pt = np.copy(pt) prev_pt = np.copy(pt) step_size = 1 iters = 0 while (step_size > precision) & (iters < max_iters): prev_pt[:] = cur_pt[:] cur_pt -= gamma * (self.apply(prev_pt) - pt) step_size = np.linalg.norm(cur_pt - prev_pt) iters += 1 if iters == max_iters: raise EstimationError( 'gradient descent for inversion of ThinPlateSpline ' 'reached maximum iterations: %d' % max_iters) return cur_pt
def fit(A, B, return_all=False): """function to fit this transform given the corresponding sets of points A & B Parameters ---------- A : numpy.array a Nx2 matrix of source points B : numpy.array a Nx2 matrix of destination points Returns ------- numpy.array a 6x1 matrix with the best fit parameters ordered M00,M01,M10,M11,B0,B1 """ if not all([A.shape[0] == B.shape[0], A.shape[1] == B.shape[1] == 2]): raise EstimationError( 'shape mismatch! A shape: {}, B shape {}'.format( A.shape, B.shape)) N = A.shape[0] # total points M = np.zeros((2 * N, 6)) Y = np.zeros((2 * N, 1)) for i in range(N): M[2 * i, :] = [A[i, 0], A[i, 1], 0, 0, 1, 0] M[2 * i + 1, :] = [0, 0, A[i, 0], A[i, 1], 0, 1] Y[2 * i] = B[i, 0] Y[2 * i + 1] = B[i, 1] (Tvec, residuals, rank, s) = np.linalg.lstsq(M, Y) if return_all: return Tvec, residuals, rank, s return Tvec
def fit(src, dst, rigid=True, **kwargs): """function to fit this transform given the corresponding sets of points src & dst Umeyama estimation of similarity transformation Parameters ---------- src : numpy.array a Nx2 matrix of source points dst : numpy.array a Nx2 matrix of destination points rigid : bool whether to constrain this transform to be rigid Returns ------- numpy.array a 6x1 matrix with the best fit parameters ordered M00,M01,M10,M11,B0,B1 """ # TODO shape assertion num, dim = src.shape src_cld = src - src.mean(axis=0) dst_cld = dst - dst.mean(axis=0) A = np.dot(dst_cld.T, src_cld) / num d = np.ones((dim, ), dtype=np.double) if np.linalg.det(A) < 0: d[dim - 1] = -1 T = np.eye(dim + 1, dtype=np.double) rank = np.linalg.matrix_rank(A) if rank == 0: raise EstimationError('zero rank matrix A unacceptable -- ' 'likely poorly conditioned') U, S, V = svd(A) if rank == dim - 1: if np.linalg.det(U) * np.linalg.det(V) > 0: T[:dim, :dim] = np.dot(U, V) else: s = d[dim - 1] d[dim - 1] = -1 T[:dim, :dim] = np.dot(U, np.dot(np.diag(d), V)) d[dim - 1] = s else: T[:dim, :dim] = np.dot(U, np.dot(np.diag(d), V.T)) fit_scale = (1.0 if rigid else 1.0 / src_cld.var(axis=0).sum() * np.dot(S, d)) T[:dim, dim] = dst.mean(axis=0) - fit_scale * np.dot(T[:dim, :dim], src.mean(axis=0).T) T[:dim, :dim] *= fit_scale return T
def fit(self, A, B): """function to fit this transform given the corresponding sets of points A & B Parameters ---------- A : numpy.array a Nx2 matrix of source points B : numpy.array a Nx2 matrix of destination points Returns ------- beta a self.lengthx2 matrix with polynomial factors normMean a self.length vector of expanded means normVar a self.length vector of expanded standard deviations """ if not all([A.shape[0] == B.shape[0], A.shape[1] == B.shape[1] == 2]): raise EstimationError( 'shape mismatch! A shape: {}, B shape {}'.format( A.shape, B.shape)) normMean = np.zeros(self.length).astype('float') normVar = np.ones(self.length).astype('float') src_exp = self.kernelExpand(A, normMean=normMean, normVar=normVar) normMean = src_exp.mean(0) normVar = src_exp.std(0) # poorly named variable src_exp = self.kernelExpand(A, normMean=normMean, normVar=normVar) xcoeff, xresiduals, xrank, xs = np.linalg.lstsq(src_exp, B[:, 0]) ycoeff, yresiduals, yrank, ys = np.linalg.lstsq(src_exp, B[:, 1]) beta = np.zeros((self.length, 2)) beta[:, 0] = xcoeff beta[:, 1] = ycoeff return beta, normMean, normVar
def estimate(self, src, dst, order=2, test_coords=True, max_tries=100, return_params=True, **kwargs): """method for setting this transformation with the best fit given the corresponding points src,dst Parameters ---------- src : numpy.array a Nx2 matrix of source points dst : numpy.array a Nx2 matrix of destination points order : int order of polynomial to fit test_coords : bool whether to test model after fitting to make sure it is good (see fitgood) max_tries : int how many times to attempt to fit the model (see fitgood) return_params : bool whether to return the parameter matrix **kwargs dictionary of keyword arguments including those that can be passed to fitgood Returns ------- numpy.array a (2,(order+1)*(order+2)/2) matrix of parameters for this matrix (or None if return_params=False) """ def fitgood(src, dst, params, atol=1e-3, rtol=0, **kwargs): """check if model produces a 'good' result Parameters ---------- src : numpy.array a Nx2 matrix of source points dst : numpy.array a Nx2 matrix of destination points params : numpy.array a Kx2 matrix of parameters atol : float absolute tolerance as in numpy.allclose for transformed sample points rtol : float relative tolerance as in numpy.allclose for transformed sample points Returns ------- bool whether the goodness condition is met """ result = Polynomial2DTransform(params=params).tform(src) t = np.allclose(result, dst, atol=atol, rtol=rtol) return t estimated = False tries = 0 while (tries < max_tries and not estimated): tries += 1 try: params = Polynomial2DTransform.fit(src, dst, order=order) except (LinAlgError, ValueError) as e: logger.debug('Encountered error {}'.format(e)) continue estimated = (fitgood(src, dst, params, **kwargs) if test_coords else True) if tries == max_tries and not estimated: raise EstimationError('Could not fit Polynomial ' 'in {} attempts!'.format(tries)) logger.debug('fit parameters in {} attempts'.format(tries)) self.params = params if return_params: return self.params
def fit(A, B, computeAffine=True): """function to fit this transform given the corresponding sets of points A & B Parameters ---------- A : numpy.array a Nx2 matrix of source points B : numpy.array a Nx2 matrix of destination points Returns ------- dMatrix : numpy.array ndims x nLm aMatrix : numpy.array ndims x ndims, affine matrix bVector : numpy.array ndims x 1, translation vector """ if not all([A.shape[0] == B.shape[0], A.shape[1] == B.shape[1] == 2]): raise EstimationError( 'shape mismatch! A shape: {}, B shape {}'.format( A.shape, B.shape)) # build displacements ndims = B.shape[1] nLm = B.shape[0] y = (B - A).flatten() # compute K # tempting to matricize this, but, nLm x nLm can get big # settle for vectorize kMatrix = np.zeros((ndims * nLm, ndims * nLm)) for i in range(nLm): r = np.linalg.norm(A[i, :] - A, axis=1) nrm = np.zeros_like(r) ind = np.argwhere(r > 1e-8) nrm[ind] = r[ind] * r[ind] * np.log(r[ind]) kMatrix[i * ndims, 0::2] = nrm kMatrix[(i * ndims + 1)::2, 1::2] = nrm # compute L lMatrix = kMatrix if computeAffine: pMatrix = np.tile(np.eye(ndims), (nLm, ndims + 1)) for d in range(ndims): pMatrix[0::2, d * ndims] = A[:, d] pMatrix[1::2, d * ndims + 1] = A[:, d] lMatrix = np.zeros( (ndims * (nLm + ndims + 1), ndims * (nLm + ndims + 1))) lMatrix[ 0: pMatrix.shape[0], kMatrix.shape[1]: kMatrix.shape[1] + pMatrix.shape[1]] = \ pMatrix pMatrix = np.transpose(pMatrix) lMatrix[kMatrix.shape[0]:kMatrix.shape[0] + pMatrix.shape[0], 0:pMatrix.shape[1]] = pMatrix lMatrix[0:ndims * nLm, 0:ndims * nLm] = kMatrix y = np.append(y, np.zeros(ndims * (ndims + 1))) wMatrix = np.linalg.solve(lMatrix, y) dMatrix = np.reshape(wMatrix[0:ndims * nLm], (ndims, nLm), order='F') aMatrix = None bVector = None if computeAffine: aMatrix = np.reshape(wMatrix[ndims * nLm:ndims * nLm + ndims * ndims], (ndims, ndims), order='F') bVector = wMatrix[ndims * nLm + ndims * ndims:] return dMatrix, aMatrix, bVector
def mesh_refine(new_src, old_src, old_dst, old_tf=None, computeAffine=True, tol=1.0, max_iter=50, nworst=10, niter=0): """recursive kernel for adaptive_mesh_estimate() Parameters ---------- new_src : numpy.array Nx2 array of new control source points. Adapts during recursion. Seeded by adaptive_mesh_estimate. old_src : numpy.array Nx2 array of orignal control source points. old_dst : numpy.array Nx2 array of orignal control destination points. old_tf : ThinPlateSplineTransform transform constructed from old_src and old_dst, passed through recursion iterations. Created if None. computeAffine : boolean whether returned transform will have aMtx tol : float in units of pixels, how close should the points match max_iter: int some limit on how many recursive attempts nworst : int per iteration, the nworst matching srcPts will be added niter : int passed through the recursion for stopping criteria Returns ------- ThinPlateSplineTransform """ if old_tf is None: old_tf = ThinPlateSplineTransform() old_tf.estimate(old_src, old_dst, computeAffine=computeAffine) new_tf = ThinPlateSplineTransform() new_tf.estimate(new_src, old_tf.tform(new_src), computeAffine=computeAffine) new_dst = new_tf.tform(old_src) delta = np.linalg.norm(new_dst - old_dst, axis=1) ind = np.argwhere(delta > tol).flatten() if ind.size == 0: return new_tf if niter == max_iter: raise EstimationError( "Max number of iterations ({}) reached in" " ThinPlateSplineTransform.mesh_refine()".format(max_iter)) sortind = np.argsort(delta[ind]) new_src = np.vstack((new_src, old_src[ind[sortind[0:nworst]]])) return ThinPlateSplineTransform.mesh_refine( new_src, old_src, old_dst, old_tf=old_tf, computeAffine=computeAffine, tol=tol, max_iter=max_iter, nworst=nworst, niter=(niter + 1))