def plot_reg_open_curve(beta1, beta2n): """ plots registration between two open curves using matplotlib :param beta: numpy ndarray of shape (2,M) of M samples :param beta: numpy ndarray of shape (2,M) of M samples :return fig: figure definition :return ax: axes definition """ T = beta1.shape[1] centroid1 = cf.calculatecentroid(beta1) beta1 = beta1 - tile(centroid1, [T, 1]).T centroid2 = cf.calculatecentroid(beta2n) beta2n = beta2n - tile(centroid2, [T, 1]).T beta2n[0, :] = beta2n[0, :] + 1.3 beta2n[1, :] = beta2n[1, :] - 0.1 fig, ax = plt.subplots() ax.plot(beta1[0, :], beta1[1, :], "r", linewidth=2) fig.hold() ax.plot(beta2n[0, :], beta2n[1, :], "b-o", linewidth=2) for j in range(0, int(T / 5)): i = j * 5 ax.plot(array([beta1[0, i], beta2n[0, i]]), array([beta1[1, i], beta2n[1, i]]), "k", linewidth=1) ax.set_aspect("equal") ax.axis("off") fig.hold() return fig, ax
def plot_reg_open_curve(beta1, beta2n): """ plots registration between two open curves using matplotlib :param beta: numpy ndarray of shape (2,M) of M samples :param beta: numpy ndarray of shape (2,M) of M samples :return fig: figure definition :return ax: axes definition """ T = beta1.shape[1] centroid1 = cf.calculatecentroid(beta1) beta1 = beta1 - tile(centroid1, [T, 1]).T centroid2 = cf.calculatecentroid(beta2n) beta2n = beta2n - tile(centroid2, [T, 1]).T beta2n[0, :] = beta2n[0, :] + 1.3 beta2n[1, :] = beta2n[1, :] - 0.1 fig, ax = plt.subplots() ax.plot(beta1[0, :], beta1[1, :], 'r', linewidth=2) fig.hold() ax.plot(beta2n[0, :], beta2n[1, :], 'b-o', linewidth=2) for j in range(0, int(T/5)): i = j*5 ax.plot(array([beta1[0, i], beta2n[0, i]]), array([beta1[1, i], beta2n[1, i]]), 'k', linewidth=1) ax.set_aspect('equal') ax.axis('off') fig.hold() return fig, ax
def oc_srvf_align(beta, mode='O'): """ This claculates the mean of a set of curves and aligns them :param beta: numpy ndarray of shape (n, M, N) describing N curves in R^M :param mode: Open ('O') or closed curve ('C') (default 'O') :rtype: tuple of numpy array :return betan: aligned curves :return qn: aligned srvf :return betamean: mean curve :return mu: mean srvf """ n, T, N = beta.shape # find mean mu, betamean, v, q = curve_karcher_mean(beta, mode=mode) qn = zeros((n, T, N)) betan = zeros((n, T, N)) centroid2 = cf.calculatecentroid(betamean) betamean = betamean - tile(centroid2, [T, 1]).T q_mu = cf.curve_to_q(betamean) # align to mean for ii in range(0, N): beta1 = beta[:, :, ii] centroid1 = cf.calculatecentroid(beta1) beta1 = beta1 - tile(centroid1, [T, 1]).T # Iteratively optimize over SO(n) x Gamma for i in range(0, 1): # Optimize over SO(n) beta1, O_hat, tau = cf.find_rotation_and_seed_coord(betamean, beta1) q1 = cf.curve_to_q(beta1) # Optimize over Gamma gam = cf.optimum_reparam_curve(q1, q_mu, 0.0) gamI = uf.invertGamma(gam) # Applying optimal re-parameterization to the second curve beta1 = cf.group_action_by_gamma_coord(beta1, gamI) # Optimize over SO(n) beta1, O_hat, tau = cf.find_rotation_and_seed_coord(betamean, beta1) qn[:, :, ii] = cf.curve_to_q(beta1) betan[:, :, ii] = beta1 align_results = collections.namedtuple('align', ['betan', 'qn', 'betamean', 'mu']) out = align_results(betan, qn, betamean, q_mu) return out
def init_path_geod(beta1, beta2, T=100, k=5): """ Initializes a path in \cal{C}. beta1, beta2 are already standardized curves. Creates a path from beta1 to beta2 in shape space, then projects to the closed shape manifold. :param beta1: numpy ndarray of shape (2,M) of M samples (first curve) :param beta2: numpy ndarray of shape (2,M) of M samples (end curve) :param T: Number of samples of curve (Default = 100) :param k: number of samples along path (Default = 5) :rtype: numpy ndarray :return alpha: a path between two q-functions :return beta: a path between two curves :return O: rotation matrix """ alpha = zeros((2, T, k)) beta = zeros((2, T, k)) dist, pathq, O = geod_sphere(beta1, beta2, k) for tau in range(0, k): alpha[:, :, tau] = cf.project_curve(pathq[:, :, tau]) x = cf.q_to_curve(alpha[:, :, tau]) a = -1 * cf.calculatecentroid(x) beta[:, :, tau] = x + tile(a, [T, 1]).T return (alpha, beta, O)
def init_path_geod(beta1, beta2, T=100, k=5): r""" Initializes a path in :math:`\cal{C}`. beta1, beta2 are already standardized curves. Creates a path from beta1 to beta2 in shape space, then projects to the closed shape manifold. :param beta1: numpy ndarray of shape (2,M) of M samples (first curve) :param beta2: numpy ndarray of shape (2,M) of M samples (end curve) :param T: Number of samples of curve (Default = 100) :param k: number of samples along path (Default = 5) :rtype: numpy ndarray :return alpha: a path between two q-functions :return beta: a path between two curves :return O: rotation matrix """ alpha = zeros((2, T, k)) beta = zeros((2, T, k)) dist, pathq, O = geod_sphere(beta1, beta2, k) for tau in range(0, k): alpha[:, :, tau] = cf.project_curve(pathq[:, :, tau]) x = cf.q_to_curve(alpha[:, :, tau]) a = -1*cf.calculatecentroid(x) beta[:, :, tau] = x + tile(a, [T, 1]).T return(alpha, beta, O)
def __init__(self, beta, mode='O', N=200, scale=True): """ fdacurve Construct an instance of this class :param beta: (n,T,K) matrix defining n dimensional curve on T samples with K curves :param mode: Open ('O') or closed curve ('C') (default 'O') :param N: resample curve to N points :param scale: scale curve to length 1 (true/false) """ self.mode = mode self.scale = scale K = beta.shape[2] n = beta.shape[0] q = zeros((n, N, K)) beta1 = zeros((n, N, K)) cent1 = zeros((n, K)) for ii in range(0, K): beta1[:, :, ii] = cf.resamplecurve(beta[:, :, ii], N, mode) a = -cf.calculatecentroid(beta1[:, :, ii]) beta1[:, :, ii] += tile(a, (N, 1)).T q[:, :, ii] = cf.curve_to_q(beta1[:, :, ii], self.scale, self.mode) cent1[:, ii] = -a self.q = q self.beta = beta1 self.cent = cent1
def srvf_align(self, parallel=False, cores=-1): """ This aligns a set of curves to the mean and computes mean if not computed :param parallel: run in parallel (default = F) :param cores: number of cores for parallel (default = -1 (all)) """ n, T, N = self.beta.shape # find mean if not hasattr(self, 'beta_mean'): self.karcher_mean() self.qn = zeros((n, T, N)) self.betan = zeros((n, T, N)) self.gams = zeros((T, N)) C = zeros((T, N)) centroid2 = cf.calculatecentroid(self.beta_mean) self.beta_mean = self.beta_mean - tile(centroid2, [T, 1]).T q_mu = cf.curve_to_q(self.beta_mean) # align to mean out = Parallel(n_jobs=-1)( delayed(align_sub)(self.beta_mean, q_mu, self.beta[:, :, n]) for n in range(N)) for ii in range(0, N): self.gams[:, ii] = out[ii][2] self.qn[:, :, ii] = cf.curve_to_q(out[ii][0]) self.betan[:, :, ii] = out[ii][0] return
def preproc_open_curve(beta, T=100): n, M, k = beta.shape q = np.zeros((n, T, k)) beta2 = np.zeros((n, T, k)) for i in range(0, k): beta1 = beta[:, :, i] beta1, scale = cf.scale_curve(beta1) beta1 = cf.resamplecurve(beta1, T) centroid1 = cf.calculatecentroid(beta1) beta1 = beta1 - np.tile(centroid1, [T, 1]).T beta2[:, :, i] = beta1 q[:, :, i] = cf.curve_to_q(beta1) return (q, beta2)
def srvf_align(self, rotation=True, parallel=False, cores=-1, method="DP"): """ This aligns a set of curves to the mean and computes mean if not computed :param rotation: compute optimal rotation (default = T) :param parallel: run in parallel (default = F) :param cores: number of cores for parallel (default = -1 (all)) :param method: method to apply optimization (default="DP") options are "DP" or "RBFGS" """ n, T, N = self.beta.shape modes = ['O', 'C'] mode = [i for i, x in enumerate(modes) if x == self.mode] if len(mode) == 0: mode = 0 else: mode = mode[0] # find mean if not hasattr(self, 'beta_mean'): self.karcher_mean() self.qn = zeros((n, T, N)) self.betan = zeros((n, T, N)) self.gams = zeros((T, N)) centroid2 = cf.calculatecentroid(self.beta_mean) self.beta_mean = self.beta_mean - tile(centroid2, [T, 1]).T # align to mean out = Parallel(n_jobs=-1)(delayed(cf.find_rotation_and_seed_unique)( self.q_mean, self.q[:, :, n], mode, rotation, method) for n in range(N)) for ii in range(0, N): self.gams[:, ii] = out[ii][2] self.qn[:, :, ii] = out[ii][0] btmp = out[ii][1].dot(self.beta[:, :, ii]) self.betan[:, :, ii] = cf.group_action_by_gamma_coord(btmp, out[ii][2]) return
def update_path(alpha, beta, gradE, delta, T=100, k=5): """ Update the path along the direction -gradE :param alpha: numpy ndarray of shape (2,M) of M samples :param beta: numpy ndarray of shape (2,M) of M samples :param gradE: numpy ndarray of shape (2,M) of M samples :param delta: gradient paramenter :param T: Number of samples of curve (Default = 100) :param k: number of samples along path (Default = 5) :rtype: numpy scalar :return alpha: updated path of srvfs :return beta: updated path of curves """ for tau in range(1, k - 1): alpha_new = alpha[:, :, tau] - delta * gradE[:, :, tau] alpha[:, :, tau] = cf.project_curve(alpha_new) x = cf.q_to_curve(alpha[:, :, tau]) a = -1 * cf.calculatecentroid(x) beta[:, :, tau] = x + tile(a, [T, 1]).T return (alpha, beta)
def srvf_align(self, parallel=False, cores=-1): """ This aligns a set of curves to the mean and computes mean if not computed :param parallel: run in parallel (default = F) :param cores: number of cores for parallel (default = -1 (all)) """ n, T, N = self.beta.shape modes = ['O', 'C'] mode = [i for i, x in enumerate(modes) if x == self.mode] if len(mode) == 0: mode = 0 else: mode = mode[0] # find mean if not hasattr(self, 'beta_mean'): self.karcher_mean() self.qn = zeros((n, T, N)) self.betan = zeros((n, T, N)) self.gams = zeros((T, N)) C = zeros((T, N)) centroid2 = cf.calculatecentroid(self.beta_mean) self.beta_mean = self.beta_mean - tile(centroid2, [T, 1]).T q_mu = cf.curve_to_q(self.beta_mean) # align to mean out = Parallel(n_jobs=-1)(delayed(cf.find_rotation_and_seed_coord)( self.beta_mean, self.beta[:, :, n], mode) for n in range(N)) for ii in range(0, N): self.gams[:, ii] = out[ii][3] self.qn[:, :, ii] = out[ii][1] self.betan[:, :, ii] = out[ii][0] return
def update_path(alpha, beta, gradE, delta, T=100, k=5): """ Update the path along the direction -gradE :param alpha: numpy ndarray of shape (2,M) of M samples :param beta: numpy ndarray of shape (2,M) of M samples :param gradE: numpy ndarray of shape (2,M) of M samples :param delta: gradient paramenter :param T: Number of samples of curve (Default = 100) :param k: number of samples along path (Default = 5) :rtype: numpy scalar :return alpha: updated path of srvfs :return beta: updated path of curves """ for tau in range(1, k-1): alpha_new = alpha[:, :, tau] - delta*gradE[:, :, tau] alpha[:, :, tau] = cf.project_curve(alpha_new) x = cf.q_to_curve(alpha[:, :, tau]) a = -1*cf.calculatecentroid(x) beta[:, :, tau] = x + tile(a, [T, 1]).T return(alpha, beta)
def karcher_mean(self, parallel=False, cores=-1): """ This calculates the mean of a set of curves :param parallel: run in parallel (default = F) :param cores: number of cores for parallel (default = -1 (all)) """ n, T, N = self.beta.shape modes = ['O', 'C'] mode = [i for i, x in enumerate(modes) if x == self.mode] if len(mode) == 0: mode = 0 else: mode = mode[0] # Initialize mu as one of the shapes mu = self.q[:, :, 0] betamean = self.beta[:, :, 0] itr = 0 gamma = zeros((T, N)) maxit = 20 sumd = zeros(maxit + 1) v = zeros((n, T, N)) normvbar = zeros(maxit + 1) delta = 0.5 tolv = 1e-4 told = 5 * 1e-3 print("Computing Karcher Mean of %d curves in SRVF space.." % N) while itr < maxit: print("updating step: %d" % (itr + 1)) if iter == maxit: print("maximal number of iterations reached") mu = mu / sqrt(cf.innerprod_q2(mu, mu)) if mode == 1: self.basis = cf.find_basis_normal(mu) else: self.basis = [] sumv = zeros((n, T)) sumd[0] = inf sumd[itr + 1] = 0 out = Parallel(n_jobs=cores)(delayed(karcher_calc)( self.beta[:, :, n], self.q[:, :, n], betamean, mu, self.basis, mode) for n in range(N)) v = zeros((n, T, N)) for i in range(0, N): v[:, :, i] = out[i][0] sumd[itr + 1] = sumd[itr + 1] + out[i][1]**2 sumv = v.sum(axis=2) # Compute average direction of tangent vectors v_i vbar = sumv / float(N) normvbar[itr] = sqrt(cf.innerprod_q2(vbar, vbar)) normv = normvbar[itr] if normv > tolv and fabs(sumd[itr + 1] - sumd[itr]) > told: # Update mu in direction of vbar mu = cos(delta * normvbar[itr]) * mu + sin( delta * normvbar[itr]) * vbar / normvbar[itr] if mode == 1: mu = cf.project_curve(mu) x = cf.q_to_curve(mu) a = -1 * cf.calculatecentroid(x) betamean = x + tile(a, [T, 1]).T else: break itr += 1 self.q_mean = mu self.beta_mean = betamean self.v = v self.qun = sumd[0:(itr + 1)] self.E = normvbar[0:(itr + 1)] return
def curve_karcher_mean(beta, mode='O'): """ This claculates the mean of a set of curves :param beta: numpy ndarray of shape (n, M, N) describing N curves in R^M :param mode: Open ('O') or closed curve ('C') (default 'O') :rtype: tuple of numpy array :return mu: mean srvf :return betamean: mean curve :return v: shooting vectors :return q: srvfs """ n, T, N = beta.shape q = zeros((n, T, N)) for ii in range(0, N): q[:, :, ii] = cf.curve_to_q(beta[:, :, ii]) modes = ['O', 'C'] mode = [i for i, x in enumerate(modes) if x == mode] if len(mode) == 0: mode = 0 else: mode = mode[0] # Initialize mu as one of the shapes mu = q[:, :, 0] betamean = beta[:, :, 0] delta = 0.5 tolv = 1e-4 told = 5*1e-3 maxit = 20 itr = 0 sumd = zeros(maxit+1) v = zeros((n, T, N)) normvbar = zeros(maxit+1) while itr < maxit: print("Iteration: %d" % itr) mu = mu / sqrt(cf.innerprod_q2(mu, mu)) sumv = zeros((2, T)) sumd[itr+1] = 0 out = Parallel(n_jobs=-1)(delayed(karcher_calc)(beta[:, :, n], q[:, :, n], betamean, mu, mode) for n in range(N)) v = zeros((n, T, N)) for i in range(0, N): v[:, :, i] = out[i][0] sumd[itr+1] = sumd[itr+1] + out[i][1]**2 sumv = v.sum(axis=2) # Compute average direction of tangent vectors v_i vbar = sumv/float(N) normvbar[itr] = sqrt(cf.innerprod_q2(vbar, vbar)) normv = normvbar[itr] if normv > tolv and fabs(sumd[itr+1]-sumd[itr]) > told: # Update mu in direction of vbar mu = cos(delta*normvbar[itr])*mu + sin(delta*normvbar[itr]) * vbar/normvbar[itr] if mode == 1: mu = cf.project_curve(mu) x = cf.q_to_curve(mu) a = -1*cf.calculatecentroid(x) betamean = x + tile(a, [T, 1]).T else: break itr += 1 return(mu, betamean, v, q)
def geod_sphere(beta1, beta2, k=5): """ This function calculates the geodesics between open curves beta1 and beta2 with k steps along path :param beta1: numpy ndarray of shape (2,M) of M samples :param beta2: numpy ndarray of shape (2,M) of M samples :param k: number of samples along path (Default = 5) :rtype: numpy ndarray :return dist: geodesic distance :return path: geodesic path :return O: rotation matrix """ lam = 0.0 elastic = 1 rotation = 1 returnpath = 1 n, T = beta1.shape beta1 = cf.resamplecurve(beta1, T) beta2 = cf.resamplecurve(beta2, T) centroid1 = cf.calculatecentroid(beta1) beta1 = beta1 - tile(centroid1, [T, 1]).T centroid2 = cf.calculatecentroid(beta2) beta2 = beta2 - tile(centroid2, [T, 1]).T q1 = cf.curve_to_q(beta1) if rotation: beta2, O1, tau = cf.find_rotation_and_seed_coord(beta1, beta2) q2 = cf.curve_to_q(beta2) else: O1 = eye(2) q2 = cf.curve_to_q(beta2) if elastic: # Find the optimal coorespondence gam = cf.optimum_reparam_curve(q2, q1, lam) gamI = uf.invertGamma(gam) # Applying optimal re-parameterization to the second curve beta2n = cf.group_action_by_gamma_coord(beta2, gamI) q2n = cf.curve_to_q(beta2n) if rotation: beta2n, O2, tau = cf.find_rotation_and_seed_coord(beta1, beta2n) centroid2 = cf.calculatecentroid(beta2n) beta2n = beta2n - tile(centroid2, [T, 1]).T q2n = cf.curve_to_q(beta2n) O = O1.dot(O2) else: q2n = q2 O = O1 # Forming geodesic between the registered curves dist = arccos(cf.innerprod_q2(q1, q2n)) if returnpath: PsiQ = zeros((n, T, k)) PsiX = zeros((n, T, k)) for tau in range(0, k): s = dist * tau / (k - 1.) PsiQ[:, :, tau] = (sin(dist-s)*q1+sin(s)*q2n)/sin(dist) PsiX[:, :, tau] = cf.q_to_curve(PsiQ[:, :, tau]) path = PsiQ else: path = 0 return(dist, path, O)
def init_path_rand(beta1, beta_mid, beta2, T=100, k=5): r""" Initializes a path in :math:`\cal{C}`. beta1, beta_mid beta2 are already standardized curves. Creates a path from beta1 to beta_mid to beta2 in shape space, then projects to the closed shape manifold. :param beta1: numpy ndarray of shape (2,M) of M samples (first curve) :param betamid: numpy ndarray of shape (2,M) of M samples (mid curve) :param beta2: numpy ndarray of shape (2,M) of M samples (end curve) :param T: Number of samples of curve (Default = 100) :param k: number of samples along path (Default = 5) :rtype: numpy ndarray :return alpha: a path between two q-functions :return beta: a path between two curves :return O: rotation matrix """ alpha = zeros((2, T, k)) beta = zeros((2, T, k)) q1 = cf.curve_to_q(beta1) q_mid = cf.curve_to_q(beta_mid) # find optimal rotation of q2 beta2, O1, tau1 = cf.find_rotation_and_seed_coord(beta1, beta2) q2 = cf.curve_to_q(beta2) # find the optimal coorespondence gam = cf.optimum_reparam_curve(q2, q1) gamI = uf.invertGamma(gam) # apply optimal reparametrization beta2n = cf.group_action_by_gamma_coord(beta2, gamI) # find optimal rotation of q2 beta2n, O2, tau1 = cf.find_rotation_and_seed_coord(beta1, beta2n) centroid2 = cf.calculatecentroid(beta2n) beta2n = beta2n - tile(centroid2, [T, 1]).T q2n = cf.curve_to_q(beta2n) O = O1.dot(O2) # Initialize a path as a geodesic through q1 --- q_mid --- q2 theta1 = arccos(cf.innerprod_q2(q1, q_mid)) theta2 = arccos(cf.innerprod_q2(q_mid, q2n)) tmp = arange(2, int((k-1)/2)+1) t = zeros(tmp.size) alpha[:, :, 0] = q1 beta[:, :, 0] = beta1 i = 0 for tau in range(2, int((k-1)/2)+1): t[i] = (tau-1.)/((k-1)/2.) qnew = (1/sin(theta1))*(sin((1-t[i])*theta1)*q1+sin(t[i]*theta1)*q_mid) alpha[:, :, tau-1] = cf.project_curve(qnew) x = cf.q_to_curve(alpha[:, :, tau-1]) a = -1*cf.calculatecentroid(x) beta[:, :, tau-1] = x + tile(a, [T, 1]).T i += 1 alpha[:, :, int((k-1)/2)] = q_mid beta[:, :, int((k-1)/2)] = beta_mid i = 0 for tau in range(int((k-1)/2)+1, k-1): qnew = (1/sin(theta2))*(sin((1-t[i])*theta2)*q_mid + sin(t[i]*theta2)*q2n) alpha[:, :, tau] = cf.project_curve(qnew) x = cf.q_to_curve(alpha[:, :, tau]) a = -1*cf.calculatecentroid(x) beta[:, :, tau] = x + tile(a, [T, 1]).T i += 1 alpha[:, :, k-1] = q2n beta[:, :, k-1] = beta2n return(alpha, beta, O)
def geod_sphere(beta1, beta2, k=5): """ This function caluclates the geodecis between open curves beta1 and beta2 with k steps along path :param beta1: numpy ndarray of shape (2,M) of M samples :param beta2: numpy ndarray of shape (2,M) of M samples :param k: number of samples along path (Default = 5) :rtype: numpy ndarray :return dist: geodesic distance :return path: geodesic path :return O: rotation matrix """ lam = 0.0 elastic = 1 rotation = 1 returnpath = 1 n, T = beta1.shape beta1 = cf.resamplecurve(beta1, T) beta2 = cf.resamplecurve(beta2, T) centroid1 = cf.calculatecentroid(beta1) beta1 = beta1 - tile(centroid1, [T, 1]).T centroid2 = cf.calculatecentroid(beta2) beta2 = beta2 - tile(centroid2, [T, 1]).T q1 = cf.curve_to_q(beta1) if rotation: beta2, O1, tau = cf.find_rotation_and_seed_coord(beta1, beta2) q2 = cf.curve_to_q(beta2) else: O1 = eye(2) q2 = cf.curve_to_q(beta2) if elastic: # Find the optimal coorespondence gam = cf.optimum_reparam_curve(q2, q1, lam) gamI = uf.invertGamma(gam) # Applying optimal re-parameterization to the second curve beta2n = cf.group_action_by_gamma_coord(beta2, gamI) q2n = cf.curve_to_q(beta2n) if rotation: beta2n, O2, tau = cf.find_rotation_and_seed_coord(beta1, beta2n) centroid2 = cf.calculatecentroid(beta2n) beta2n = beta2n - tile(centroid2, [T, 1]).T q2n = cf.curve_to_q(beta2n) O = O1.dot(O2) else: q2n = q2 O = O1 # Forming geodesic between the registered curves dist = arccos(cf.innerprod_q2(q1, q2n)) if returnpath: PsiQ = zeros((n, T, k)) PsiX = zeros((n, T, k)) for tau in range(0, k): s = dist * tau / (k - 1.0) PsiQ[:, :, tau] = (sin(dist - s) * q1 + sin(s) * q2n) / sin(dist) PsiX[:, :, tau] = cf.q_to_curve(PsiQ[:, :, tau]) path = PsiQ else: path = 0 return (dist, path, O)
def predict(self, newdata=None): """ This function performs prediction on regression model on new data if available or current stored data in object Usage: obj.predict() obj.predict(newdata) :param newdata: dict containing new data for prediction (needs the keys below, if None predicts on training data) :type newdata: dict :param beta: (n, M,N) matrix of curves :param y: truth if available """ T = self.warp_data.beta_mean.shape[1] if newdata != None: beta = newdata['beta'] y = newdata['y'] n = beta.shape[2] beta1 = np.zeros(beta.shape) q = np.zeros(beta.shape) for ii in range(0,n): if (beta.shape[1] != T): beta1[:,:,ii] = cf.resamplecurve(beta[:,:,ii],T) else: beta1[:,:,ii] = beta[:,:,ii] a = -cf.calculatecentroid(beta1[:,:,ii]) beta1[:,:,ii] += np.tile(a, (T,1)).T q[:,:,ii] = cf.curve_to_q(beta1[:,:,ii])[0] mu = self.warp_data.q_mean v = np.zeros(q.shape) for ii in range(0,n): qn_t, R, gamI = cf.find_rotation_and_seed_unique(mu, q[:,:,ii], 0, self.rotation) qn_t = qn_t / np.sqrt(cf.innerprod_q2(qn_t,qn_t)) q1dotq2 = cf.innerprod_q2(mu,qn_t) if (q1dotq2 > 1): q1dotq2 = 1 d = np.arccos(q1dotq2) u = qn_t - q1dotq2*mu normu = np.sqrt(cf.innerprod_q2(u,u)) if (normu>1e-4): v[:,:,ii] = u*np.arccos(q1dotq2)/normu else: v[:,:,ii] = np.zeros(qn_t.shape) Utmp = self.warp_data.U.T no = self.warp_data.U.shape[1] VM = np.mean(self.warp_data.v,2) VM = VM.flatten() x = np.zeros((no,n)) for i in range(0,n): tmp = v[:,:,i] tmpv1 = tmp.flatten() x[:,i] = Utmp.dot((tmpv1- VM)) self.y_pred = np.zeros(n) for ii in range(0,n): self.y_pred[ii] = self.alpha + np.dot(x[:,ii],self.b) if y is None: self.SSE = np.nan else: self.SSE = np.sum((y-self.y_pred)**2) else: n = self.warp_data.coef.shape[1] self.y_pred = np.zeros(n) for ii in range(0,n): self.y_pred[ii] = self.alpha + np.dot(self.warp_data.coef[:,ii],self.b) self.SSE = np.sum((self.y-self.y_pred)**2) return
def init_path_rand(beta1, beta_mid, beta2, T=100, k=5): """ Initializes a path in \cal{C}. beta1, beta_mid beta2 are already standardized curves. Creates a path from beta1 to beta_mid to beta2 in shape space, then projects to the closed shape manifold. :param beta1: numpy ndarray of shape (2,M) of M samples (first curve) :param betamid: numpy ndarray of shape (2,M) of M samples (mid curve) :param beta2: numpy ndarray of shape (2,M) of M samples (end curve) :param T: Number of samples of curve (Default = 100) :param k: number of samples along path (Default = 5) :rtype: numpy ndarray :return alpha: a path between two q-functions :return beta: a path between two curves :return O: rotation matrix """ alpha = zeros((2, T, k)) beta = zeros((2, T, k)) q1 = cf.curve_to_q(beta1) q_mid = cf.curve_to_q(beta_mid) # find optimal rotation of q2 beta2, O1, tau1 = cf.find_rotation_and_seed_coord(beta1, beta2) q2 = cf.curve_to_q(beta2) # find the optimal coorespondence gam = cf.optimum_reparam_curve(q2, q1) gamI = uf.invertGamma(gam) # apply optimal reparametrization beta2n = cf.group_action_by_gamma_coord(beta2, gamI) # find optimal rotation of q2 beta2n, O2, tau1 = cf.find_rotation_and_seed_coord(beta1, beta2n) centroid2 = cf.calculatecentroid(beta2n) beta2n = beta2n - tile(centroid2, [T, 1]).T q2n = cf.curve_to_q(beta2n) O = O1.dot(O2) # Initialize a path as a geodesic through q1 --- q_mid --- q2 theta1 = arccos(cf.innerprod_q2(q1, q_mid)) theta2 = arccos(cf.innerprod_q2(q_mid, q2n)) tmp = arange(2, int((k - 1) / 2) + 1) t = zeros(tmp.size) alpha[:, :, 0] = q1 beta[:, :, 0] = beta1 i = 0 for tau in range(2, int((k - 1) / 2) + 1): t[i] = (tau - 1.0) / ((k - 1) / 2.0) qnew = (1 / sin(theta1)) * (sin((1 - t[i]) * theta1) * q1 + sin(t[i] * theta1) * q_mid) alpha[:, :, tau - 1] = cf.project_curve(qnew) x = cf.q_to_curve(alpha[:, :, tau - 1]) a = -1 * cf.calculatecentroid(x) beta[:, :, tau - 1] = x + tile(a, [T, 1]).T i += 1 alpha[:, :, int((k - 1) / 2)] = q_mid beta[:, :, int((k - 1) / 2)] = beta_mid i = 0 for tau in range(int((k - 1) / 2) + 1, k - 1): qnew = (1 / sin(theta2)) * (sin((1 - t[i]) * theta2) * q_mid + sin(t[i] * theta2) * q2n) alpha[:, :, tau] = cf.project_curve(qnew) x = cf.q_to_curve(alpha[:, :, tau]) a = -1 * cf.calculatecentroid(x) beta[:, :, tau] = x + tile(a, [T, 1]).T i += 1 alpha[:, :, k - 1] = q2n beta[:, :, k - 1] = beta2n return (alpha, beta, O)
def curve_principal_directions(betamean, mu, K, mode='O', no=3, N=5): """ Computes principal direction of variation specified by no. N is Number of shapes away from mean. Creates 2*N+1 shape sequence :param betamean: numpy ndarray of shape (n, M) describing the mean curve :param mu: numpy ndarray of shape (n, M) describing the mean srvf :param K: numpy ndarray of shape (M, M) describing the covariance :param mode: Open ('O') or closed curve ('C') (default 'O') :param no: number of direction (default 3) :param N: number of shapes (2*N+1) (default 5) :rtype: tuple of numpy array :return pd: principal directions """ n, T = betamean.shape modes = ['O', 'C'] mode = [i for i, x in enumerate(modes) if x == mode] if len(mode) == 0: mode = 0 else: mode = mode[0] U, s, V = svd(K) qarray = empty((no, 2*N+1), dtype=object) qarray1 = empty(N, dtype=object) qarray2 = empty(N, dtype=object) pd = empty((no, 2*N+1), dtype=object) pd1 = empty(N, dtype=object) pd2 = empty(N, dtype=object) for m in range(0, no): princDir = vstack((U[0:T, m], U[T:2*T, m])) v = sqrt(s[m]) * princDir q1 = mu epsilon = 2./N # Forward direction from mean for i in range(0, N): normv = sqrt(cf.innerprod_q2(v, v)) if normv < 1e-4: q2 = mu else: q2 = cos(epsilon*normv)*q1 + sin(epsilon*normv)*v/normv if mode == 1: q2 = cf.project_curve(q2) qarray1[i] = q2 p = cf.q_to_curve(q2) centroid1 = -1*cf.calculatecentroid(p) beta_scaled, scale = cf.scale_curve(p + tile(centroid1, [T, 1]).T) pd1[i] = beta_scaled # Parallel translate tangent vector basis2 = cf.find_basis_normal(q2) v = cf.parallel_translate(v, q1, q2, basis2, mode) q1 = q2 # Backward direction from mean v = -sqrt(s[m])*princDir q1 = mu for i in range(0, N): normv = sqrt(cf.innerprod_q2(v, v)) if normv < 1e-4: q2 = mu else: q2 = cos(epsilon*normv)*q1+sin(epsilon*normv)*v/normv if mode == 1: q2 = cf.project_curve(q2) qarray2[i] = q2 p = cf.q_to_curve(q2) centroid1 = -1*cf.calculatecentroid(p) beta_scaled, scale = cf.scale_curve(p + tile(centroid1, [T, 1]).T) pd2[i] = beta_scaled # Parallel translate tangent vector basis2 = cf.find_basis_normal(q2) v = cf.parallel_translate(v, q1, q2, basis2, mode) q1 = q2 for i in range(0, N): qarray[m, i] = qarray2[(N-1)-i] pd[m, i] = pd2[(N-1)-i] qarray[m, N] = mu centroid1 = -1*cf.calculatecentroid(betamean) beta_scaled, scale = cf.scale_curve(betamean + tile(centroid1, [T, 1]).T) pd[m, N] = beta_scaled for i in range(N+1, 2*N+1): qarray[m, i] = qarray1[i-(N+1)] pd[m, i] = pd1[i-(N+1)] return(pd)
def geod_sphere(beta1, beta2, k=5, scale=False, rotation=True, center=True): """ This function calculates the geodesics between open curves beta1 and beta2 with k steps along path :param beta1: numpy ndarray of shape (2,M) of M samples :param beta2: numpy ndarray of shape (2,M) of M samples :param k: number of samples along path (Default = 5) :param scale: include length (Default = False) :param rotation: include rotation (Default = True) :param center: center curves at origin (Default = True) :rtype: numpy ndarray :return dist: geodesic distance :return path: geodesic path :return O: rotation matrix """ lam = 0.0 returnpath = 1 n, T = beta1.shape if center: centroid1 = cf.calculatecentroid(beta1) beta1 = beta1 - tile(centroid1, [T, 1]).T centroid2 = cf.calculatecentroid(beta2) beta2 = beta2 - tile(centroid2, [T, 1]).T q1, len1, lenq1 = cf.curve_to_q(beta1) if scale: q2, len2, lenq2 = cf.curve_to_q(beta2) beta2, q2n, O1, gamI = cf.find_rotation_and_seed_coord(beta1, beta2, rotation=rotation) # Forming geodesic between the registered curves val = cf.innerprod_q2(q1, q2n) if val > 1: if val < 1.0001: # assume numerical error import warnings warnings.warn( f"Corrected a numerical error in geod_sphere: rounded {val} to 1" ) val = 1 else: raise Exception( f"innerpod_q2 computed an inner product of {val} which is much greater than 1" ) elif val < -1: if val > -1.0001: # assume numerical error import warnings warnings.warn( f"Corrected a numerical error in geod_sphere: rounded {val} to -1" ) val = -1 else: raise Exception( f"innerpod_q2 computed an inner product of {val} which is much less than -1" ) dist = arccos(val) if isnan(dist): raise Exception("geod_sphere computed a dist value which is NaN") if returnpath: PsiQ = zeros((n, T, k)) PsiX = zeros((n, T, k)) for tau in range(0, k): if tau == 0: tau1 = 0 else: tau1 = tau / (k - 1.) s = dist * tau1 if dist > 0: PsiQ[:, :, tau] = (sin(dist - s) * q1 + sin(s) * q2n) / sin(dist) elif dist == 0: PsiQ[:, :, tau] = (1 - tau1) * q1 + (tau1) * q2n else: raise Exception("geod_sphere computed a negative distance") if scale: scl = len1**(1 - tau1) * len2**(tau1) else: scl = 1 beta = scl * cf.q_to_curve(PsiQ[:, :, tau]) if center: centroid = cf.calculatecentroid(beta) beta = beta - tile(centroid, [T, 1]).T PsiX[:, :, tau] = beta path = PsiX else: path = 0 return (dist, path, PsiQ)