From 559ee8ff1b49e67006bacadd037f71c6dc5bcc55 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sat, 29 Dec 2018 02:18:45 -0500 Subject: [PATCH 01/39] Add func to calc distances between each lmk in 2 lmk sets. Init some tests for it --- morphops/__init__.py | 2 +- morphops/lmk_util.py | 19 ++++++++++++++++++- tests/test_lmk_util.py | 15 ++++++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/morphops/__init__.py b/morphops/__init__.py index b26a12c..cac7b0e 100644 --- a/morphops/__init__.py +++ b/morphops/__init__.py @@ -20,7 +20,7 @@ # Not sure if prepending morphops to module is necessary, but makes it easier to import into jupyter for testing. from .lmk_util import \ - transpose, num_coords, num_lmks, num_lmk_sets, ssqd + transpose, num_coords, num_lmks, num_lmk_sets, ssqd, distance_matrix from .io import \ MopsFileReadError, MopsFileWriteError, read_dta, write_dta from .procrustes import \ diff --git a/morphops/lmk_util.py b/morphops/lmk_util.py index da1a168..298ce74 100644 --- a/morphops/lmk_util.py +++ b/morphops/lmk_util.py @@ -75,4 +75,21 @@ def ssqd(X): ssq = 0 for i in np.arange(n_lmk_sets - 1): ssq += np.sum(np.square(X[i:] - X[i])) - return ssq*1.0/n_lmk_sets \ No newline at end of file + return ssq*1.0/n_lmk_sets + +def distance_matrix(X,Y): + """For (p1,k)-shaped X and (p2,k)-shaped Y, returns the (p1,p2) matrix + where the element at [i,j] is the distance between X[i,:] and Y[j,:]. + + This is basically a matrix version of the following code. + + .. code-block:: python + + for i in range(len(X)): + for j in range(len(Y)): + d[i,j] = np.sqrt(np.sum(np.square(np.array(X[i]) - np.array(Y[j])))) + """ + XX = np.tile(np.sum(np.square(X),axis=1),(len(Y),1)).T + YY = np.tile(np.sum(np.square(Y),axis=1),(len(X),1)) + XY = np.dot(X, transpose(Y)) + return np.sqrt(XX + YY - 2*XY) \ No newline at end of file diff --git a/tests/test_lmk_util.py b/tests/test_lmk_util.py index c41e446..fd12672 100644 --- a/tests/test_lmk_util.py +++ b/tests/test_lmk_util.py @@ -42,6 +42,12 @@ "when X is the unit square + it's pi/4 rotated form, as " "4*[(1-cos(pi/4))^2 + cos(pi/4)^2]")] +distance_matrix_data = [ + (np.zeros((4,2)), np.zeros((5,2)), np.zeros((4,5)), + "when X is (p1,k) zeros and Y is (p2,k) zeros, as (pi,p2) zeros"), + ([[0,0],[0,1]],[[1,1],[1,0]],[[np.sqrt(2),1],[1,np.sqrt(2)]], + "when X and Y are oppo sides of a unit square, as 1s and sqrt(2)s")] + @pytest.mark.parametrize("X, scn", num_lmk_sets_fail_data) def test_num_lmk_sets_fail(X, scn): print("num_lmk_sets should fail -", scn) @@ -88,4 +94,11 @@ def test_ssqd_fail(X, err_msg, scn): def test_ssqd(X, ans, scn): print("ssqd should give the sum of squared differences " "between all pairs of matrices -", scn) - assert np.isclose(lmk_util.ssqd(X), ans) \ No newline at end of file + assert np.isclose(lmk_util.ssqd(X), ans) + +@pytest.mark.parametrize("X, Y, ans, scn", distance_matrix_data) +def test_distance_matrix(X, Y, ans, scn): + print("distance_matrix should give the distance between each lmk in X " + "and Y when -", scn) + assert np.allclose(lmk_util.distance_matrix(X,Y), ans) + From a5dc193b47006769103df0db10afa2b6c6868441 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sat, 29 Dec 2018 06:40:55 -0500 Subject: [PATCH 02/39] Import tps funcs upto tps warping, Provide via __init__ --- morphops/__init__.py | 1 + morphops/tps.py | 85 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 morphops/tps.py diff --git a/morphops/__init__.py b/morphops/__init__.py index cac7b0e..a5063b7 100644 --- a/morphops/__init__.py +++ b/morphops/__init__.py @@ -23,6 +23,7 @@ transpose, num_coords, num_lmks, num_lmk_sets, ssqd, distance_matrix from .io import \ MopsFileReadError, MopsFileWriteError, read_dta, write_dta +from .tps import * from .procrustes import \ get_position, get_scale, remove_position, remove_scale, \ rotate, opa, gpa diff --git a/morphops/tps.py b/morphops/tps.py new file mode 100644 index 0000000..77d21a3 --- /dev/null +++ b/morphops/tps.py @@ -0,0 +1,85 @@ +"""Provides thin-plate splines related operations and algorithms. +""" + +import numpy as np +import math +import morphops.lmk_util as lmk_util +import warnings + +def K_matrix(X, Y=None): + """Calculates the upper-right (p,p) submatrix of the (p+k+1,p+k+1)-shaped + L matrix. + + Parameters + ---------- + X : (p,2) or (p,3) shaped array-like + + A (p,k) array of p landmarks in k=2 or k=3 dimensions for one specimen. + + Y : (p,2) or (p,3) shaped array-like, optional + + A (p,k) array of p landmarks in k=2 or k=3 dimensions for one specimen. + `Y` must have the same k as X (but can have a different p). + """ + num_coords = lmk_util.num_coords(X) + if (num_coords != 2) and (num_coords != 3): + raise ValueError("The input matrix must have landmarks with " + "coordinates in either 2 or 3 dimensions.") + if Y is None: + Y = X + r = lmk_util.distance_matrix(X, Y) + if (num_coords == 2): + r_sqd = np.square(r) + # Make a copy of r_sqd where 0->1. This copy will be passed to log. + # This way log(1) will be 0 and we wont get NaN and warnings. + r_sqd_cl = np.copy(r_sqd) + r_sqd_cl[np.isclose(r_sqd_cl,0)] = 1 + return -np.multiply(r_sqd, np.log(r_sqd_cl)) + # else num_coords is 3 + return -r + +def P_matrix(X): + """Makes the minor diagonal submatrix P of the (p+k+1,p+k+1)-shaped L + matrix. + """ + ones = np.ones(lmk_util.num_lmks(X)) + return np.column_stack((ones, X)) + +def L_matrix(X): + """Makes the (p+k+1,p+k+1)-shaped L matrix that gets inverted when + calculating the thin-plate spline "over" or "from" `X`. + """ + n_coords = lmk_util.num_coords(X) + n_lmks = lmk_util.num_lmks(X) + K = K_matrix(X) + P = P_matrix(X) + L = np.zeros((n_lmks + n_coords + 1, n_lmks + n_coords + 1)) + L[0:n_lmks,0:n_lmks] = K + L[0:n_lmks,n_lmks:] = P + L[n_lmks:,0:n_lmks] = np.transpose(P) + return L + +def tps_coefs(X, Y): + """Finds the tps coefficients for the tps function that interpolates from X + to Y. + """ + n_coords = lmk_util.num_coords(X) + n_lmks = lmk_util.num_lmks(X) + V = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) + L = L_matrix(X) + L_inv = np.linalg.inv(L) + Q = np.matmul(L_inv, V) + # return W and A. + return Q[0:n_lmks], Q[n_lmks:] + +def tps_warp(X, Y, pts): + """Maps points `pts` to their image under the tps function generated by + :func:`tps_coefs` of `X` and `Y`. + """ + W, A = tps_coefs(X, Y) + U = K_matrix(pts, X) + P = P_matrix(pts) + # The warped pts are the affine part + the non-uniform part + return np.matmul(P,A) + np.matmul(U,W) + + From 186cb37f254573c3d4feedd3c2bd39700d9af3bf Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sat, 29 Dec 2018 06:41:35 -0500 Subject: [PATCH 03/39] Commit auto generated summary for tps --- docs/source/_autosummary/morphops.tps.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs/source/_autosummary/morphops.tps.rst diff --git a/docs/source/_autosummary/morphops.tps.rst b/docs/source/_autosummary/morphops.tps.rst new file mode 100644 index 0000000..13c8bd9 --- /dev/null +++ b/docs/source/_autosummary/morphops.tps.rst @@ -0,0 +1,10 @@ +morphops.tps +============ + +.. contents:: + :local: + +.. automodule:: morphops.tps + +.. Members +.. ======= \ No newline at end of file From 62b9396e25fc29017144facc84ab51786aaa70ef Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sat, 29 Dec 2018 06:45:50 -0500 Subject: [PATCH 04/39] Reorder modules in sidebar --- morphops/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/morphops/__init__.py b/morphops/__init__.py index a5063b7..2c95e5a 100644 --- a/morphops/__init__.py +++ b/morphops/__init__.py @@ -13,9 +13,10 @@ .. autosummary:: :toctree: _autosummary - lmk_util - io procrustes + tps + io + lmk_util """ # Not sure if prepending morphops to module is necessary, but makes it easier to import into jupyter for testing. From 30f11231d7704cbe69db9a89f09a730d5a5b646e Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sat, 29 Dec 2018 07:09:19 -0500 Subject: [PATCH 05/39] Add pytest-cov to test coverage locally --- .gitignore | 5 ++++- tox.ini | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e8b6601..6d82b88 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,7 @@ trying_stuff docs/build # Tox -.tox \ No newline at end of file +.tox + +# pytest +.coverage \ No newline at end of file diff --git a/tox.ini b/tox.ini index 4f3e2a5..648ba91 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,16 @@ python = 3.6: py36 3.7: python3.7 +[testenv:py36] +deps = + pytest + pytest-cov +commands = + pytest -q -s tests + pytest --cov=morphops tests/ + [testenv] -deps = pytest -commands = pytest -q -s tests \ No newline at end of file +deps = + pytest +commands = + pytest -q -s tests \ No newline at end of file From bd821e78373f0a7cbbad391d0d3a52fdf8a3c930 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sat, 29 Dec 2018 09:27:32 -0500 Subject: [PATCH 06/39] Update docs and local var names --- morphops/tps.py | 112 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 99 insertions(+), 13 deletions(-) diff --git a/morphops/tps.py b/morphops/tps.py index 77d21a3..85a9096 100644 --- a/morphops/tps.py +++ b/morphops/tps.py @@ -1,4 +1,19 @@ """Provides thin-plate splines related operations and algorithms. + +Given two sets of points, the thin-plate spline can interpolate from one to the +other in a manner that minimizes the "integral bending norm"[bookstein89]_. + +Importantly, it has a remarkable connection to Kendall's shape space in the +following way: The non-zero eigenvectors of the bending energy matrix form an +orthonormal basis in the tangent space of shape coordinates [bookstein96]_. + +References +---------- +.. [bookstein89] Bookstein, F.L., 1989. Principal warps: Thin-plate splines + and the decomposition of deformations. IEEE Transactions on pattern + analysis and machine intelligence, 11(6), pp.567-585. +.. [bookstein96] Bookstein, F.L., 1996. Biometrics, biomathematics and the + morphometric synthesis. Bulletin of mathematical biology, 58(2), p.313. """ import numpy as np @@ -14,12 +29,23 @@ def K_matrix(X, Y=None): ---------- X : (p,2) or (p,3) shaped array-like - A (p,k) array of p landmarks in k=2 or k=3 dimensions for one specimen. + A (p,k) array of p points in k=2 or k=3 dimensions. - Y : (p,2) or (p,3) shaped array-like, optional + Y : (m,2) or (m,3) shaped array-like, optional + A (m,k) array of p points in k=2 or k=3 dimensions. `Y` must have the + same k as `X`. - A (p,k) array of p landmarks in k=2 or k=3 dimensions for one specimen. - `Y` must have the same k as X (but can have a different p). + If `Y` is `None`, it is just set to `X`. + + Returns + ------- + K : np.ndarray + A (p,p) array where the element at [i,j] is :math:`U(\|X_i - Y_j\|)`. The definition of U depends on k. + + In particular, if k = 2, then :math:`U(r) = r^2 \log(r^2)`, else + :math:`U(r) = r`. + + Note that using :math:`\\alpha U(r)` instead of :math:`U(r)` for some constant :math:`\\alpha \in \mathbb{R}` will not matter when warping, as it will become inverted when calculating :math:`L^{-1}`, and get multiplied by itself when calculating the non-uniform part of the warp. """ num_coords = lmk_util.num_coords(X) if (num_coords != 2) and (num_coords != 3): @@ -34,20 +60,43 @@ def K_matrix(X, Y=None): # This way log(1) will be 0 and we wont get NaN and warnings. r_sqd_cl = np.copy(r_sqd) r_sqd_cl[np.isclose(r_sqd_cl,0)] = 1 - return -np.multiply(r_sqd, np.log(r_sqd_cl)) + return np.multiply(r_sqd, np.log(r_sqd_cl)) # else num_coords is 3 - return -r + return r def P_matrix(X): """Makes the minor diagonal submatrix P of the (p+k+1,p+k+1)-shaped L matrix. + + Basically just stacks a column of 1s before the coordinate columns in `X`. + + Parameters + ---------- + X : (p,2) or (p,3) shaped array-like + A (p,k) array of p points in k=2 or k=3 dimensions. + + Returns + ------- + P : np.ndarray + A (p,k+1) array, which is 1 in the first column, and exactly `X` in the + remaining columns. """ ones = np.ones(lmk_util.num_lmks(X)) return np.column_stack((ones, X)) def L_matrix(X): """Makes the (p+k+1,p+k+1)-shaped L matrix that gets inverted when - calculating the thin-plate spline "over" or "from" `X`. + calculating the thin-plate spline "from" `X`. + + Parameters + ---------- + X : (p,2) or (p,3) shaped array-like + A (p,k) array of p landmarks in k=2 or k=3 dimensions for one specimen. + + Returns + ------- + L : np.ndarray + A (p+k+1,p+k+1) array of the form [[K | P][P.T | 0]]. """ n_coords = lmk_util.num_coords(X) n_lmks = lmk_util.num_lmks(X) @@ -60,21 +109,58 @@ def L_matrix(X): return L def tps_coefs(X, Y): - """Finds the tps coefficients for the tps function that interpolates from X - to Y. + """Finds the thin-plate spline coefficients for the thin-plate spline + function that interpolates from X to Y. + + Parameters + ---------- + X : (p,2) or (p,3) shaped array-like + A (p,k) array of p points in k=2 or k=3 dimensions. + + Y : (p,2) or (p,3) shaped array-like + A (p,k) array of p points in k=2 or k=3 dimensions. `Y` must have the + same shape as `X`. + + Returns + ------- + W : np.ndarray + A (p,k) array of weights for the non-affine part of the spline. + + A : np.ndarray + A (k+1,k) array of weights for the affine part of the spline. """ n_coords = lmk_util.num_coords(X) n_lmks = lmk_util.num_lmks(X) - V = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) + Y_0 = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) L = L_matrix(X) L_inv = np.linalg.inv(L) - Q = np.matmul(L_inv, V) + Q = np.matmul(L_inv, Y_0) # return W and A. return Q[0:n_lmks], Q[n_lmks:] def tps_warp(X, Y, pts): - """Maps points `pts` to their image under the tps function generated by - :func:`tps_coefs` of `X` and `Y`. + """Maps points `pts` to their image under the thin-plate spline function generated by :func:`tps_coefs` of `X` and `Y`. + + A point :math:`(a,b)` gets mapped to + .. math:: f(a,b) + + Parameters + ---------- + X : (p,2) or (p,3) shaped array-like + A (p,k) array of p points in k=2 or k=3 dimensions. + + Y : (p,2) or (p,3) shaped array-like + A (p,k) array of p points in k=2 or k=3 dimensions. `Y` must have the + same shape as `X`. + + pts : (m,2) or (m,3) shaped array-like, optional + A (m,k) array of m points in k=2 or k=3 dimensions. `pts` must have the + same coordinate dimensions k as `X`. + + Returns + ------- + warped_pts : (m,2) or (m,3) shaped array-like, optional + A (m,k) array of points corresponding to the image of `pts` under the thin-plate spline produced by `X`, `Y`. """ W, A = tps_coefs(X, Y) U = K_matrix(pts, X) From 572d1f333674583e4e6a68c8d662528a4143f7d4 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 09:43:16 -0500 Subject: [PATCH 07/39] Do Y stuff later to make clear the independence of L_inv from Y in code. --- morphops/tps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/morphops/tps.py b/morphops/tps.py index 85a9096..4ac3231 100644 --- a/morphops/tps.py +++ b/morphops/tps.py @@ -131,9 +131,9 @@ def tps_coefs(X, Y): """ n_coords = lmk_util.num_coords(X) n_lmks = lmk_util.num_lmks(X) - Y_0 = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) L = L_matrix(X) L_inv = np.linalg.inv(L) + Y_0 = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) Q = np.matmul(L_inv, Y_0) # return W and A. return Q[0:n_lmks], Q[n_lmks:] From ca58cd052a0c08f708e2b506b7190b77b10699f8 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 09:45:18 -0500 Subject: [PATCH 08/39] Add a func to return the bending energy matrix of a set of landmarks, apart from tps_coefs. I might need the bending mat in semilmks relaxation. Here I'm just returning the pxp submat of L inverse. In the 89 bookstein paper, I think he returns Linv*K*Linv, but I don't think that definition is used elsewhere. Not sure. --- morphops/tps.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/morphops/tps.py b/morphops/tps.py index 4ac3231..1df3b6f 100644 --- a/morphops/tps.py +++ b/morphops/tps.py @@ -108,6 +108,24 @@ def L_matrix(X): L[n_lmks:,0:n_lmks] = np.transpose(P) return L +def bending_energy_matrix(X): + """Returns the upper right (pxp) submatrix of L^(-1). + + Parameters + ---------- + X : (p,2) or (p,3) shaped array-like + A (p,k) array of p landmarks in k=2 or k=3 dimensions for one specimen. + + Returns + ------- + L_inv : np.ndarray + The upper right (p,p) submatrix of the inverse of the `L_matrix` of `X`. + """ + n_lmks = lmk_util.num_lmks(X) + L = L_matrix(X) + L_inv = np.linalg.inv(L) + return L_inv[0:n_lmks,0:n_lmks] + def tps_coefs(X, Y): """Finds the thin-plate spline coefficients for the thin-plate spline function that interpolates from X to Y. From a1ed371784b5ebd3cd6ad99a3eb1af5e2d645888 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 09:46:12 -0500 Subject: [PATCH 09/39] Write some tests for tps. I've really not tested things too thoroughly. For eg, not doing any checks on how the non-affine part of the warp behaves. --- tests/test_tps.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/test_tps.py diff --git a/tests/test_tps.py b/tests/test_tps.py new file mode 100644 index 0000000..d12d15b --- /dev/null +++ b/tests/test_tps.py @@ -0,0 +1,67 @@ +import pytest +import numpy as np +import morphops.tps as tps +from .helpers import make_haus, make_ngon, get_2d_rot + +K_matrix_2_data = [(np.zeros((5,2)), None, np.zeros((5,5)), + "X is (p,2) zeros. The result should be (p,p) zeros."), + (np.ones((5,2)), None, np.zeros((5,5)), + "X is (p,2) ones. The result should be (p,p) zeros."), + (make_ngon(4), None, + [[0, 2*np.log(2), 8*np.log(2), 2*np.log(2)], + [2*np.log(2), 0, 2*np.log(2), 8*np.log(2)], + [8*np.log(2), 2*np.log(2), 0, 2*np.log(2)], + [2*np.log(2), 8*np.log(2), 2*np.log(2), 0]], + "X is square of side sqrt(2). Result should be same as in 89 " + "paper, sec E.")] + +K_matrix_3_data = [(np.column_stack((np.ones(4),make_ngon(4))), None, + [[0, np.sqrt(2), 2, np.sqrt(2)], + [np.sqrt(2), 0, np.sqrt(2), 2], + [2, np.sqrt(2), 0, np.sqrt(2)], + [np.sqrt(2), 2, np.sqrt(2), 0]], + "X is square of side sqrt(2). Result should be the distance " + "matrix.")] + +tps_coefs_affine_data = [(make_ngon(4), make_ngon(4) + 1, + np.zeros((4,2)), [[1,1],[1,0],[0,1]], "X is a square of side sqrt(2), Y = X + (1,1). Result should be that W is all " + "zeros and A is a row of ones stacked on identity." + ), + (make_ngon(5), make_ngon(5, np.pi/4), + np.zeros((5,2)),np.row_stack(([0,0],get_2d_rot(-np.pi/4))), "X is a pentagon, Y = XR. Result should be that W is all " + "zeros and A is a row of zeros stacked on R.T." + )] + +tps_warp_affine_data = [(make_ngon(4), make_ngon(4) + 1, + make_ngon(4), make_ngon(4) + 1, + "X is a square of side sqrt(2), Y = X + (1,1), pts = X. " + "Result should Y."), + (make_ngon(5), make_ngon(5, np.pi/4), + make_ngon(4), np.matmul(make_ngon(4), get_2d_rot(-np.pi/4)), + "X is a pentagon, Y = XR, pts = square. Result should be " + "pts*R.T." + )] + +@pytest.mark.parametrize("X, Y, ans, scn", K_matrix_2_data) +def test_K_matrix_2(X, Y, ans, scn): + print("K_matrix should evaluate the rbf at the distance between each " + "lmk in X and Y when -", scn) + assert np.allclose(tps.K_matrix(X,Y), ans) + +@pytest.mark.parametrize("X, Y, ans, scn", K_matrix_3_data) +def test_K_matrix_3(X, Y, ans, scn): + print("K_matrix should evaluate the rbf at the distance between each " + "lmk in X and Y when -", scn) + assert np.allclose(tps.K_matrix(X,Y), ans) + +@pytest.mark.parametrize("X, Y, W_ans, A_ans, scn", tps_coefs_affine_data) +def test_tps_coefs(X, Y, W_ans, A_ans, scn): + print("tps_coefs should evaluate spline weights W, A when -", scn) + W, A = tps.tps_coefs(X, Y) + assert np.allclose(W, W_ans) + assert np.allclose(A, A_ans) + +@pytest.mark.parametrize("X, Y, pts, warped_pts_ans, scn", tps_warp_affine_data) +def test_tps_warp(X, Y, pts, warped_pts_ans, scn): + print("tps_warp should given warped pts when -", scn) + assert np.allclose(tps.tps_warp(X,Y,pts), warped_pts_ans) From 9a7558a312fa3b576dba2088a4d5f247d2d7306e Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 12:23:05 -0500 Subject: [PATCH 10/39] Update README w high-level sumry of ops and tps info, Ren Usage to Usage Examples --- README.rst | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 02896d5..2f6c16a 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,34 @@ .. image:: https://travis-ci.com/vaipatel/morphops.svg?branch=master :target: https://travis-ci.com/vaipatel/morphops +| + +.. contents:: + :local: + +| + Welcome to Morphops! ==================== Morphops implements common operations and algorithms for geometric morphometrics, in python 3. +Some high-level operations in the current version are + +* Centering, rescaling data: \ + :meth:`remove_position(lmk_sets) `, + :meth:`remove_scale(lmk_sets) ` +* Rigid Rotation, Ordinary and Generalized Procrustes alignment: \ + :meth:`rotate(src_sets,tar_sets) `, + :meth:`opa(src_set,tar_set) `, + :meth:`gpa(all_sets) ` +* Thin-plate spline warping: \ + :meth:`tps_warp(X, Y, pts) ` +* Reading from and writing to \*.dta files: \ + :meth:`read_dta(fn) `, + :meth:`write_dta(fn,lmk_sets,names) ` + Dependencies ------------ @@ -17,8 +39,8 @@ Installation :code:`pip install morphops` -Usage ------ +Usage Examples +-------------- .. code-block:: python @@ -34,3 +56,7 @@ Usage # res['X0_ald'] contains the aligned A, B, C. # res['X0_mu'] contains the mean of the aligned A, B, C. + + # Create a Thin-plate Spline warp from A to B and warp C. + warped_C = mops.tps_warp(A, B, C) + # warped_C contains the image of the pts in C under the TPS warp. From 75f27d9bd1d183853c0423dacf35e65e72ad28cc Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 12:31:03 -0500 Subject: [PATCH 11/39] Rem trailing whitespace --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 2f6c16a..4a57d1e 100644 --- a/README.rst +++ b/README.rst @@ -53,7 +53,7 @@ Usage Examples # Perform Generalized Procrustes alignment to align A, B, C. # :func:`gpa` is in the procrustes module. res = mops.gpa([A,B,C]) - + # res['X0_ald'] contains the aligned A, B, C. # res['X0_mu'] contains the mean of the aligned A, B, C. From 1667df4f1b8522074678afeb8ee1b9fa7f1acb5a Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 12:40:48 -0500 Subject: [PATCH 12/39] Try using tox instead of tox-travis in travis yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 73a5a14..e9007eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: dist: xenial sudo: true -install: pip install tox-travis +install: pip install tox script: tox From 48807fb10a8680ffad7ff15b4505de0e6964a764 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 12:44:18 -0500 Subject: [PATCH 13/39] Switch back to tox-travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e9007eb..73a5a14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: dist: xenial sudo: true -install: pip install tox +install: pip install tox-travis script: tox From bef565c66c56dc6cb98f0f8db8a7cb1c825855b7 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 13:00:34 -0500 Subject: [PATCH 14/39] Bump to 0.1.5 --- docs/source/conf.py | 2 +- morphops/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 16568a0..7dd3e2c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '0.1' # The full version, including alpha/beta/rc tags -release = '0.1.4' +release = '0.1.5' # -- General configuration --------------------------------------------------- diff --git a/morphops/_version.py b/morphops/_version.py index bad32e9..e2888cc 100644 --- a/morphops/_version.py +++ b/morphops/_version.py @@ -1 +1 @@ -__version__ = '0.1.4' \ No newline at end of file +__version__ = '0.1.5' \ No newline at end of file From 49c2972f2716c4758c7a98573a6bf55d4a08ba5d Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 13:07:15 -0500 Subject: [PATCH 15/39] For numpy ver to be at least 1.10.0, owing to use of matmul. See https://stackoverflow.com/a/38682136/3501947 or the np.matmul docs at https://docs.scipy.org/doc/numpy/reference/generated/numpy.matmul.html --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f51da5f..c7eb2cd 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,6 @@ 'License :: OSI Approved :: MIT License', 'Topic :: Scientific/Engineering' ], - install_requires= ['numpy'], + install_requires= ['numpy>=1.10.0'], include_package_data=True ) \ No newline at end of file From 391df997ca851df79a49a55c7eeddc6d9cc30ed8 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 13:15:05 -0500 Subject: [PATCH 16/39] Bump to 0.1.6 (still buggy wrt numpy ver and travis failing, not sure if related) --- docs/source/conf.py | 2 +- morphops/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 7dd3e2c..8385285 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '0.1' # The full version, including alpha/beta/rc tags -release = '0.1.5' +release = '0.1.6' # -- General configuration --------------------------------------------------- diff --git a/morphops/_version.py b/morphops/_version.py index e2888cc..63eb0cb 100644 --- a/morphops/_version.py +++ b/morphops/_version.py @@ -1 +1 @@ -__version__ = '0.1.5' \ No newline at end of file +__version__ = '0.1.6' \ No newline at end of file From 04f7e4d268a0be5f23b6d8304be461b17706f49e Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 13:32:28 -0500 Subject: [PATCH 17/39] Bump to 0.1.7a0 to test on pypi --- docs/source/conf.py | 2 +- morphops/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 8385285..356056d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '0.1' # The full version, including alpha/beta/rc tags -release = '0.1.6' +release = '0.1.7a0' # -- General configuration --------------------------------------------------- diff --git a/morphops/_version.py b/morphops/_version.py index 63eb0cb..0ed6ece 100644 --- a/morphops/_version.py +++ b/morphops/_version.py @@ -1 +1 @@ -__version__ = '0.1.6' \ No newline at end of file +__version__ = '0.1.7a0' \ No newline at end of file From c4160a642a5f260e8584ae848ac12f96829cfdd0 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 13:33:57 -0500 Subject: [PATCH 18/39] Rem some stuff frm README to fix rendering issues on GH and PyPi --- README.rst | 23 +++-------------- README_for_docs.rst | 62 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 README_for_docs.rst diff --git a/README.rst b/README.rst index 4a57d1e..d94db72 100644 --- a/README.rst +++ b/README.rst @@ -1,13 +1,6 @@ .. image:: https://travis-ci.com/vaipatel/morphops.svg?branch=master :target: https://travis-ci.com/vaipatel/morphops -| - -.. contents:: - :local: - -| - Welcome to Morphops! ==================== @@ -16,18 +9,10 @@ morphometrics, in python 3. Some high-level operations in the current version are -* Centering, rescaling data: \ - :meth:`remove_position(lmk_sets) `, - :meth:`remove_scale(lmk_sets) ` -* Rigid Rotation, Ordinary and Generalized Procrustes alignment: \ - :meth:`rotate(src_sets,tar_sets) `, - :meth:`opa(src_set,tar_set) `, - :meth:`gpa(all_sets) ` -* Thin-plate spline warping: \ - :meth:`tps_warp(X, Y, pts) ` -* Reading from and writing to \*.dta files: \ - :meth:`read_dta(fn) `, - :meth:`write_dta(fn,lmk_sets,names) ` +* Centering, rescaling data +* Rigid Rotation, Ordinary and Generalized Procrustes alignment +* Thin-plate spline warping +* Reading from and writing to \*.dta files Dependencies ------------ diff --git a/README_for_docs.rst b/README_for_docs.rst new file mode 100644 index 0000000..4a57d1e --- /dev/null +++ b/README_for_docs.rst @@ -0,0 +1,62 @@ +.. image:: https://travis-ci.com/vaipatel/morphops.svg?branch=master + :target: https://travis-ci.com/vaipatel/morphops + +| + +.. contents:: + :local: + +| + +Welcome to Morphops! +==================== + +Morphops implements common operations and algorithms for geometric +morphometrics, in python 3. + +Some high-level operations in the current version are + +* Centering, rescaling data: \ + :meth:`remove_position(lmk_sets) `, + :meth:`remove_scale(lmk_sets) ` +* Rigid Rotation, Ordinary and Generalized Procrustes alignment: \ + :meth:`rotate(src_sets,tar_sets) `, + :meth:`opa(src_set,tar_set) `, + :meth:`gpa(all_sets) ` +* Thin-plate spline warping: \ + :meth:`tps_warp(X, Y, pts) ` +* Reading from and writing to \*.dta files: \ + :meth:`read_dta(fn) `, + :meth:`write_dta(fn,lmk_sets,names) ` + +Dependencies +------------ + +* numpy + +Installation +------------ + +:code:`pip install morphops` + +Usage Examples +-------------- + +.. code-block:: python + + import morphops as mops + # Create 3 landmark sets, each having 5 landmarks in 2 dimensions. + A = [[0,0],[2,0],[2,2],[1,3],[0,2]] + B = [[0.1,-0.1],[2,0],[2.3,1.8],[1,3],[0.4,2]] + C = [[-0.1,-0.1],[2.1,0],[2,1.8],[0.9,3.1],[-0.4,2.1]] + + # Perform Generalized Procrustes alignment to align A, B, C. + # :func:`gpa` is in the procrustes module. + res = mops.gpa([A,B,C]) + + # res['X0_ald'] contains the aligned A, B, C. + # res['X0_mu'] contains the mean of the aligned A, B, C. + + # Create a Thin-plate Spline warp from A to B and warp C. + warped_C = mops.tps_warp(A, B, C) + # warped_C contains the image of the pts in C under the TPS warp. From 06f6a6edc066484a8f3922fe2012f116728799ff Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 14:25:09 -0500 Subject: [PATCH 19/39] Bump to 0.1.7a1 --- docs/source/conf.py | 2 +- morphops/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 356056d..dde15f9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '0.1' # The full version, including alpha/beta/rc tags -release = '0.1.7a0' +release = '0.1.7a1' # -- General configuration --------------------------------------------------- diff --git a/morphops/_version.py b/morphops/_version.py index 0ed6ece..9dac3c1 100644 --- a/morphops/_version.py +++ b/morphops/_version.py @@ -1 +1 @@ -__version__ = '0.1.7a0' \ No newline at end of file +__version__ = '0.1.7a1' \ No newline at end of file From 41444040e0df038027205405d2a1e6c4cffe2c66 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 22:17:08 -0500 Subject: [PATCH 20/39] Add setup_requires, install_requires to see if it fixes pip issue --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c7eb2cd..2910890 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,8 @@ 'License :: OSI Approved :: MIT License', 'Topic :: Scientific/Engineering' ], - install_requires= ['numpy>=1.10.0'], + setup_requires= ['numpy >= 1.10.0'], + install_requires= ['numpy >= 1.10.0'], + python_requires='>=3.5', include_package_data=True ) \ No newline at end of file From c84f31e84254af96d04cee15eceff49ad559a9f3 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Sun, 30 Dec 2018 22:17:24 -0500 Subject: [PATCH 21/39] Bump to 0.1.7a2 --- docs/source/conf.py | 2 +- morphops/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index dde15f9..b073e34 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '0.1' # The full version, including alpha/beta/rc tags -release = '0.1.7a1' +release = '0.1.7a2' # -- General configuration --------------------------------------------------- diff --git a/morphops/_version.py b/morphops/_version.py index 9dac3c1..e4ab6db 100644 --- a/morphops/_version.py +++ b/morphops/_version.py @@ -1 +1 @@ -__version__ = '0.1.7a1' \ No newline at end of file +__version__ = '0.1.7a2' \ No newline at end of file From e155c9e7b433cb53ba58d5bd5029bd7718ac0f51 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 04:34:28 -0500 Subject: [PATCH 22/39] Use dot instead of matmul --- morphops/tps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/morphops/tps.py b/morphops/tps.py index 1df3b6f..a9001fe 100644 --- a/morphops/tps.py +++ b/morphops/tps.py @@ -152,7 +152,7 @@ def tps_coefs(X, Y): L = L_matrix(X) L_inv = np.linalg.inv(L) Y_0 = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) - Q = np.matmul(L_inv, Y_0) + Q = np.dot(L_inv, Y_0) # return W and A. return Q[0:n_lmks], Q[n_lmks:] From 19e9e4f3634ee6a928655cf5663ce58798d9c6f0 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 08:32:46 -0500 Subject: [PATCH 23/39] Move creating Y_0 above like before. I'm doing this to be able to use `solve` or `lstsq` next (instead of calculating the `inv`). --- morphops/tps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/morphops/tps.py b/morphops/tps.py index a9001fe..7dc60f2 100644 --- a/morphops/tps.py +++ b/morphops/tps.py @@ -149,9 +149,9 @@ def tps_coefs(X, Y): """ n_coords = lmk_util.num_coords(X) n_lmks = lmk_util.num_lmks(X) + Y_0 = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) L = L_matrix(X) L_inv = np.linalg.inv(L) - Y_0 = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) Q = np.dot(L_inv, Y_0) # return W and A. return Q[0:n_lmks], Q[n_lmks:] From 533a4e4c0fff6a815cc1d7ab5072e7abd5377456 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 08:35:18 -0500 Subject: [PATCH 24/39] Use np.linalg.solve instead of calculating the inv and matrix multiplying. --- morphops/tps.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/morphops/tps.py b/morphops/tps.py index 7dc60f2..13e352e 100644 --- a/morphops/tps.py +++ b/morphops/tps.py @@ -151,8 +151,7 @@ def tps_coefs(X, Y): n_lmks = lmk_util.num_lmks(X) Y_0 = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) L = L_matrix(X) - L_inv = np.linalg.inv(L) - Q = np.dot(L_inv, Y_0) + Q = np.linalg.solve(L, Y_0) # return W and A. return Q[0:n_lmks], Q[n_lmks:] From ab948e6416536dd405eaae5825a9ea8514a9c235 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 08:45:54 -0500 Subject: [PATCH 25/39] Use lstsq instead of solve with rcond None to see if it fixes travis --- morphops/tps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/morphops/tps.py b/morphops/tps.py index 13e352e..98e08e3 100644 --- a/morphops/tps.py +++ b/morphops/tps.py @@ -151,7 +151,7 @@ def tps_coefs(X, Y): n_lmks = lmk_util.num_lmks(X) Y_0 = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) L = L_matrix(X) - Q = np.linalg.solve(L, Y_0) + Q = np.linalg.lstsq(L, Y_0, rcond="warn")[0] # return W and A. return Q[0:n_lmks], Q[n_lmks:] From 3d8652155037a9347ab1a45dfc0c86e1e1b4ec38 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 08:59:19 -0500 Subject: [PATCH 26/39] Spread multiline string over multiple lines, put pentagons in glob vars. --- tests/test_tps.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_tps.py b/tests/test_tps.py index d12d15b..c304660 100644 --- a/tests/test_tps.py +++ b/tests/test_tps.py @@ -3,6 +3,9 @@ import morphops.tps as tps from .helpers import make_haus, make_ngon, get_2d_rot +pentagon = make_ngon(5) +pentagon_45 = make_ngon(5, np.pi/4) + K_matrix_2_data = [(np.zeros((5,2)), None, np.zeros((5,5)), "X is (p,2) zeros. The result should be (p,p) zeros."), (np.ones((5,2)), None, np.zeros((5,5)), @@ -24,11 +27,13 @@ "matrix.")] tps_coefs_affine_data = [(make_ngon(4), make_ngon(4) + 1, - np.zeros((4,2)), [[1,1],[1,0],[0,1]], "X is a square of side sqrt(2), Y = X + (1,1). Result should be that W is all " + np.zeros((4,2)), [[1,1],[1,0],[0,1]], "X is a square of side " + "sqrt(2), Y = X + (1,1). Result should be that W is all " "zeros and A is a row of ones stacked on identity." ), - (make_ngon(5), make_ngon(5, np.pi/4), - np.zeros((5,2)),np.row_stack(([0,0],get_2d_rot(-np.pi/4))), "X is a pentagon, Y = XR. Result should be that W is all " + (pentagon, pentagon_45, + np.zeros((5,2)),np.row_stack(([0,0],get_2d_rot(-np.pi/4))), "X " + "is a pentagon, Y = XR. Result should be that W is all " "zeros and A is a row of zeros stacked on R.T." )] @@ -36,8 +41,8 @@ make_ngon(4), make_ngon(4) + 1, "X is a square of side sqrt(2), Y = X + (1,1), pts = X. " "Result should Y."), - (make_ngon(5), make_ngon(5, np.pi/4), - make_ngon(4), np.matmul(make_ngon(4), get_2d_rot(-np.pi/4)), + (pentagon, pentagon_45, + make_ngon(4), np.dot(make_ngon(4), get_2d_rot(-np.pi/4)), "X is a pentagon, Y = XR, pts = square. Result should be " "pts*R.T." )] From 753ae7380a02cd598fd56fa0b02490f571aac6bf Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 10:19:49 -0500 Subject: [PATCH 27/39] Only test with squares for now, put squares in glob vars. --- tests/test_tps.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/test_tps.py b/tests/test_tps.py index c304660..5bc1072 100644 --- a/tests/test_tps.py +++ b/tests/test_tps.py @@ -4,13 +4,15 @@ from .helpers import make_haus, make_ngon, get_2d_rot pentagon = make_ngon(5) -pentagon_45 = make_ngon(5, np.pi/4) +pentagon_45 = make_ngon(5, np.pi/4.0) +fourgon = make_ngon(4) +fourgon_45 = make_ngon(4, np.pi/4.0) K_matrix_2_data = [(np.zeros((5,2)), None, np.zeros((5,5)), "X is (p,2) zeros. The result should be (p,p) zeros."), (np.ones((5,2)), None, np.zeros((5,5)), "X is (p,2) ones. The result should be (p,p) zeros."), - (make_ngon(4), None, + (fourgon, None, [[0, 2*np.log(2), 8*np.log(2), 2*np.log(2)], [2*np.log(2), 0, 2*np.log(2), 8*np.log(2)], [8*np.log(2), 2*np.log(2), 0, 2*np.log(2)], @@ -18,7 +20,7 @@ "X is square of side sqrt(2). Result should be same as in 89 " "paper, sec E.")] -K_matrix_3_data = [(np.column_stack((np.ones(4),make_ngon(4))), None, +K_matrix_3_data = [(np.column_stack((np.ones(4),fourgon)), None, [[0, np.sqrt(2), 2, np.sqrt(2)], [np.sqrt(2), 0, np.sqrt(2), 2], [2, np.sqrt(2), 0, np.sqrt(2)], @@ -26,25 +28,25 @@ "X is square of side sqrt(2). Result should be the distance " "matrix.")] -tps_coefs_affine_data = [(make_ngon(4), make_ngon(4) + 1, +tps_coefs_affine_data = [(fourgon, fourgon + 1, np.zeros((4,2)), [[1,1],[1,0],[0,1]], "X is a square of side " "sqrt(2), Y = X + (1,1). Result should be that W is all " "zeros and A is a row of ones stacked on identity." ), - (pentagon, pentagon_45, - np.zeros((5,2)),np.row_stack(([0,0],get_2d_rot(-np.pi/4))), "X " - "is a pentagon, Y = XR. Result should be that W is all " - "zeros and A is a row of zeros stacked on R.T." + (fourgon, fourgon_45, + np.zeros((4,2)),np.row_stack(([0,0],get_2d_rot(-np.pi/4))), + "X is a square of side sqrt(2), Y = XR. Result should be that " + "W is all zeros and A is a row of zeros stacked on R.T." )] -tps_warp_affine_data = [(make_ngon(4), make_ngon(4) + 1, - make_ngon(4), make_ngon(4) + 1, +tps_warp_affine_data = [(fourgon, fourgon + 1, + fourgon, fourgon + 1, "X is a square of side sqrt(2), Y = X + (1,1), pts = X. " "Result should Y."), - (pentagon, pentagon_45, - make_ngon(4), np.dot(make_ngon(4), get_2d_rot(-np.pi/4)), - "X is a pentagon, Y = XR, pts = square. Result should be " - "pts*R.T." + (fourgon, fourgon_45, + fourgon, np.dot(fourgon, get_2d_rot(-np.pi/4)), + "X is a square of side sqrt(2), Y = XR, pts = square. Result " + "should be pts*R.T." )] @pytest.mark.parametrize("X, Y, ans, scn", K_matrix_2_data) From 289586e873d8f8d68c2c79d18bc8754f6a2de087 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 10:33:49 -0500 Subject: [PATCH 28/39] Explicitly raise if L_inv*Y contains NaNs --- morphops/tps.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/morphops/tps.py b/morphops/tps.py index 98e08e3..7595546 100644 --- a/morphops/tps.py +++ b/morphops/tps.py @@ -151,7 +151,9 @@ def tps_coefs(X, Y): n_lmks = lmk_util.num_lmks(X) Y_0 = np.row_stack((Y, np.zeros((n_coords+1,n_coords)))) L = L_matrix(X) - Q = np.linalg.lstsq(L, Y_0, rcond="warn")[0] + Q = np.linalg.solve(L, Y_0) + if np.any(np.isnan(Q)): + raise ValueError("The result of L_inv*Y contained NaN values.") # return W and A. return Q[0:n_lmks], Q[n_lmks:] From 33bebc226624e59a66b4f630b4fb93b1d3e598b5 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 10:42:29 -0500 Subject: [PATCH 29/39] Try sneaking pentagon back into warp test --- tests/test_tps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_tps.py b/tests/test_tps.py index 5bc1072..2309270 100644 --- a/tests/test_tps.py +++ b/tests/test_tps.py @@ -43,9 +43,9 @@ fourgon, fourgon + 1, "X is a square of side sqrt(2), Y = X + (1,1), pts = X. " "Result should Y."), - (fourgon, fourgon_45, + (pentagon, pentagon_45, fourgon, np.dot(fourgon, get_2d_rot(-np.pi/4)), - "X is a square of side sqrt(2), Y = XR, pts = square. Result " + "X is a pentagon, Y = XR, pts = square. Result " "should be pts*R.T." )] From 224dc3019183ebed948b2b6ca77f988b31af8b41 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 10:45:28 -0500 Subject: [PATCH 30/39] Bump to 0.1.7a3 --- docs/source/conf.py | 2 +- morphops/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index b073e34..b9a4baa 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '0.1' # The full version, including alpha/beta/rc tags -release = '0.1.7a2' +release = '0.1.7a3' # -- General configuration --------------------------------------------------- diff --git a/morphops/_version.py b/morphops/_version.py index e4ab6db..eb55f3e 100644 --- a/morphops/_version.py +++ b/morphops/_version.py @@ -1 +1 @@ -__version__ = '0.1.7a2' \ No newline at end of file +__version__ = '0.1.7a3' \ No newline at end of file From c3a3a5e906a19f63ae1e0dc083963cbac32f4b14 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 11:17:38 -0500 Subject: [PATCH 31/39] Rem py3.5 from test, Require mkl for install I'm requiring mkl cuz the intermitten fail on travis is very likely due to something wrong with OpenBLAS. See https://github.com/statsmodels/statsmodels/issues/5396. I'm removing py3.5 because requiring mkl in install trips up tox, (or I guess more specifically python setup.py, and it throws a DistUtilsError about not finding numpy>=1.10.0). --- .travis.yml | 1 - setup.py | 2 +- tox.ini | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 73a5a14..0943161 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ sudo: false language: python python: - - "3.5" - "3.6" matrix: diff --git a/setup.py b/setup.py index 2910890..d424206 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ 'Topic :: Scientific/Engineering' ], setup_requires= ['numpy >= 1.10.0'], - install_requires= ['numpy >= 1.10.0'], + install_requires= ['numpy >= 1.10.0', 'mkl'], python_requires='>=3.5', include_package_data=True ) \ No newline at end of file diff --git a/tox.ini b/tox.ini index 648ba91..b232658 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,8 @@ [tox] -envlist = py35,py36,python3.7 +envlist = py36,python3.7 [travis] python = - 3.5: py35 3.6: py36 3.7: python3.7 From b38dcf279ece41f40cf6486c1532ecdfa52d87b4 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 21:40:08 -0500 Subject: [PATCH 32/39] Rem mkl, just test on osx for the moment Removing mkl cuz it does not seem to help with the intermittent NaN issue on travis. Trying to just test on osx. --- .travis.yml | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0943161..9c17a79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ sudo: false language: python +os: + - osx + python: - "3.6" diff --git a/setup.py b/setup.py index d424206..2910890 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ 'Topic :: Scientific/Engineering' ], setup_requires= ['numpy >= 1.10.0'], - install_requires= ['numpy >= 1.10.0', 'mkl'], + install_requires= ['numpy >= 1.10.0'], python_requires='>=3.5', include_package_data=True ) \ No newline at end of file From b0da2f37698ae95462fa8fc997700843de2c1574 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 21:44:35 -0500 Subject: [PATCH 33/39] Rem 3.7 from travis bcoz does not exist on osx apparently --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9c17a79..eaed76e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,11 @@ os: python: - "3.6" -matrix: - include: - - python: 3.7 - dist: xenial - sudo: true +# matrix: +# include: +# - python: 3.7 +# dist: xenial +# sudo: true install: pip install tox-travis From 532b10bebae10a22db83a7a7912362ccd779656d Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 21:48:32 -0500 Subject: [PATCH 34/39] Rem os osx --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index eaed76e..c32dbc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,6 @@ sudo: false language: python -os: - - osx - python: - "3.6" From 7151050ad9e71901ebe4d443cf1d6f3f2954cce1 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Mon, 31 Dec 2018 21:50:37 -0500 Subject: [PATCH 35/39] Bump to 0.1.7a4 --- docs/source/conf.py | 2 +- morphops/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index b9a4baa..16fd7c2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '0.1' # The full version, including alpha/beta/rc tags -release = '0.1.7a3' +release = '0.1.7a4' # -- General configuration --------------------------------------------------- diff --git a/morphops/_version.py b/morphops/_version.py index eb55f3e..60a5572 100644 --- a/morphops/_version.py +++ b/morphops/_version.py @@ -1 +1 @@ -__version__ = '0.1.7a3' \ No newline at end of file +__version__ = '0.1.7a4' \ No newline at end of file From 1e65effdedef35bb8f6c1146a31f96fec27321cf Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Tue, 1 Jan 2019 01:44:30 -0500 Subject: [PATCH 36/39] Bite bullet and switch back to square. I think this might be better because of the problem is really only when I have 5 or more lmks, and this way I can have more oses. --- tests/test_tps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_tps.py b/tests/test_tps.py index 2309270..5bc1072 100644 --- a/tests/test_tps.py +++ b/tests/test_tps.py @@ -43,9 +43,9 @@ fourgon, fourgon + 1, "X is a square of side sqrt(2), Y = X + (1,1), pts = X. " "Result should Y."), - (pentagon, pentagon_45, + (fourgon, fourgon_45, fourgon, np.dot(fourgon, get_2d_rot(-np.pi/4)), - "X is a pentagon, Y = XR, pts = square. Result " + "X is a square of side sqrt(2), Y = XR, pts = square. Result " "should be pts*R.T." )] From ea86d74e8ac9371d5f4cfe90fb141d184ad0b46a Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Tue, 1 Jan 2019 01:44:47 -0500 Subject: [PATCH 37/39] Bring back 3.5 and 3.7 for now. --- .travis.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index c32dbc6..73a5a14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,14 @@ sudo: false language: python python: + - "3.5" - "3.6" -# matrix: -# include: -# - python: 3.7 -# dist: xenial -# sudo: true +matrix: + include: + - python: 3.7 + dist: xenial + sudo: true install: pip install tox-travis From 1122b0399fe513ddcf74cb1e4ad6444b141b9951 Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Tue, 1 Jan 2019 01:53:51 -0500 Subject: [PATCH 38/39] Include README_for_docs instead of README in the index.rst for docs. The README_for_docs also contains a contents local directive at the beginning, as well as links for the high-level ops. --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 06cf69f..124a6e3 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,7 +3,7 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -.. include:: ../../README.rst +.. include:: ../../README_for_docs.rst morphops module =============== From 4956949fcbdd5964501216b8e66ff425f9a334da Mon Sep 17 00:00:00 2001 From: Vaibhav Patel Date: Tue, 1 Jan 2019 01:55:21 -0500 Subject: [PATCH 39/39] Bump to 0.1.7 --- docs/source/conf.py | 2 +- morphops/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 16fd7c2..dc75692 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '0.1' # The full version, including alpha/beta/rc tags -release = '0.1.7a4' +release = '0.1.7' # -- General configuration --------------------------------------------------- diff --git a/morphops/_version.py b/morphops/_version.py index 60a5572..0876fc4 100644 --- a/morphops/_version.py +++ b/morphops/_version.py @@ -1 +1 @@ -__version__ = '0.1.7a4' \ No newline at end of file +__version__ = '0.1.7' \ No newline at end of file