def fit_rotation(tps_fn, src_pts, tgt_pts):
    _, d = src_pts.shape
    tps_local = ThinPlateSpline(d)
    tps_local.trans_g = tps_fn.trans_g
    src_median = np.median(src_pts, axis=0)
    src_pts = src_pts - src_median # so we rotate around 0
    tps_local.trans_g += src_median # this will translate the points back
    tgt_kd = sp_spat.KDTree(tgt_pts)
    results = []
    rot_matrices = [np.matrix([[np.cos(theta), -1*np.sin(theta), 0], 
                           [np.sin(theta), np.cos(theta), 0], 
                           [0, 0, 1]]) 
                for theta in np.linspace(-1*np.pi, np.pi, DISCRETIZATION_LEVEL)]
    for rot in rot_matrices:
        tps_local.lin_ag = rot
        rot_pts = tps_fn.transform_points(src_pts)
        rot_kd = sp_spat.KDTree(rot_pts)
        # compute bi_directional closest-point cost
        dist_mat = rot_kd.sparse_distance_matrix(tgt_kd, MAX_CP_DIST)
        min_tgt_dists = MAX_CP_DIST*np.ones(len(tgt_kd.data))
        min_rot_dists = MAX_CP_DIST*np.ones(len(rot_kd.data))        
        for (rot_pt, tgt_pt), dist in dist_mat.iteritems():
            min_rot_dists[rot_pt] = min(min_rot_dists[rot_pt], dist)
            min_tgt_dists[tgt_pt] = min(min_tgt_dists[tgt_pt], dist)
        cost = np.linalg.norm(np.r_[min_tgt_dists, min_rot_dists])
        results.append(cost)
    tps_fn.lin_ag = rot_matrices[np.argmin(results)]
    tps_fn.trans_g= np.dot(tps_fn.lin_ag, -1*src_median) \
                             + tps_local.trans_g[None,:]
    tps_fn.trans_g = np.asarray(tps_fn.trans_g)[0,:]
# @profile
Returns:
dataset/ETHPy150Open rll/lfd/lfd/rapprentice/registration.py/fit_rotation--crossover with--dataset/ETHPy150Open meraki-analytics/cassiopeia/cassiopeia/type/core/currentgame.py/Game.duration