{"action":"created","comment":{"url":"https://api.github.com/repos/nipy/dipy/pulls/comments/29599781","id":29599781,"diff_hunk":"@@ -0,0 +1,776 @@\n+import numpy as np\n+import scipy as sp\n+import scipy.ndimage as ndimage\n+from dipy.core.ndindex import ndindex\n+from dipy.data import get_data\n+import dipy.align.vector_fields as vf\n+from dipy.align.transforms import regtransforms, Transform\n+from dipy.align.mattes import (MattesBase,\n+                               cubic_spline,\n+                               cubic_spline_derivative,\n+                               sample_domain_regular)\n+from numpy.testing import (assert_array_equal,\n+                           assert_array_almost_equal,\n+                           assert_almost_equal,\n+                           assert_equal,\n+                           assert_raises)\n+\n+\n+def create_random_image_pair_2d(nr, nc, nvals):\n+    r\"\"\" Create a pair of images with an arbitrary, non-uniform joint\n+    distribution\n+    \"\"\"\n+    sh = (nr, nc)\n+    static = np.random.randint(0, nvals, nr*nc).reshape(sh)\n+\n+    # This is just a simple way of making joint the distribution non-uniform\n+    moving = static.copy()\n+    moving += np.random.randint(0, nvals//2, nr*nc).reshape(sh) - nvals//4\n+\n+    # This is just a simple way of making joint the distribution non-uniform\n+    static = moving.copy()\n+    static += np.random.randint(0, nvals//2, nr*nc).reshape(sh) - nvals//4\n+\n+    return static.astype(np.float64), moving.astype(np.float64)\n+\n+\n+def create_random_image_pair_3d(ns, nr, nc, nvals):\n+    r\"\"\" Create a pair of images with an arbitrary, non-uniform joint\n+    distribution\n+    \"\"\"\n+    sh = (ns, nr, nc)\n+    static = np.random.randint(0, nvals, ns*nr*nc).reshape(sh)\n+\n+    # This is just a simple way of making  the distribution non-uniform\n+    moving = static.copy()\n+    moving += np.random.randint(0, nvals//2, ns*nr*nc).reshape(sh) - nvals//4\n+\n+    # This is just a simple way of making  the distribution non-uniform\n+    static = moving.copy()\n+    static += np.random.randint(0, nvals//2, ns*nr*nc).reshape(sh) - nvals//4\n+\n+    return static.astype(np.float64), moving.astype(np.float64)\n+\n+\n+def test_cubic_spline():\n+    #Cubic spline as defined in [Mattes03] eq. (3)\n+    #\n+    # [Mattes03] Mattes, D., Haynor, D. R., Vesselle, H., Lewellen, T. K.,\n+    #            & Eubank, W. PET-CT image registration in the chest using\n+    #            free-form deformations. IEEE Transactions on Medical Imaging,\n+    #            22(1), 120-8, 2003.\n+    input = []\n+    expected = []\n+    epsilon = 1e-9\n+    for epsilon in [-1e-9, 0.0, 1e-9]:\n+        for t in [-2.0, -1.0, 0.0, 1.0, 2.0]:\n+            x = t + epsilon\n+            input.append(x)\n+            absx = np.abs(x)\n+            sqrx = x * x\n+            if absx < 1:\n+                expected.append((4.0 - 6*sqrx + 3.0 * (absx**3))/6.0)\n+            elif absx < 2:\n+                expected.append(((2 - absx)**3)/6.0)\n+            else:\n+                expected.append(0.0)\n+    actual = cubic_spline(np.array(input, dtype=np.float64))\n+    assert_array_almost_equal(actual, np.array(expected, dtype=np.float64))\n+\n+\n+def test_cubic_spline_derivative():\n+    # Test derivative of the cubic spline, as defined in [Mattes03] eq. (3) by\n+    # comparing the analytical and numerical derivatives\n+    #\n+    # [Mattes03] Mattes, D., Haynor, D. R., Vesselle, H., Lewellen, T. K.,\n+    #            & Eubank, W. PET-CT image registration in the chest using\n+    #            free-form deformations. IEEE Transactions on Medical Imaging,\n+    #            22(1), 120-8, 2003.\n+    input = []\n+    expected = []\n+    epsilon = 1e-9\n+    for epsilon in [-1e-9, 0.0, 1e-9]:\n+        for t in [-2.0, -1.0, 0.0, 1.0, 2.0]:\n+            x = t + epsilon\n+            input.append(x)\n+    h = 1e-6\n+    input = np.array(input)\n+    input_h = input + h\n+    s = np.array(cubic_spline(input))\n+    s_h = np.array(cubic_spline(input_h))\n+    expected = (s_h - s)/h\n+    actual = cubic_spline_derivative(input)\n+    assert_array_almost_equal(actual, expected)\n+\n+\n+def test_mattes_base():\n+    # Test the simple functionality of MattesBase,\n+    # the gradients and computation of the joint intensity distribution\n+    # will be tested independently\n+    for nbins in [15, 30, 50]:\n+        for min_int in [-10.0, 0.0, 10.0]:\n+            for intensity_range in [0.1, 1.0, 10.0]:\n+                factor = 1\n+                max_int = min_int + intensity_range\n+                M = MattesBase(nbins)\n+                # Make a pair of 4-pixel images, introduce +/- 1 values\n+                # that will be excluded using a mask\n+                static = np.array([min_int - 1.0, min_int,\n+                                   max_int, max_int + 1.0])\n+                # Multiply by an arbitrary value (make the ranges different)\n+                moving = factor * np.array([min_int, min_int - 1.0,\n+                                  max_int + 1.0, max_int])\n+                # Create a mask to exclude the invalid values (beyond min and\n+                # max computed above)\n+                static_mask = np.array([0, 1, 1, 0])\n+                moving_mask = np.array([1, 0, 0, 1])\n+\n+                M.setup(static, moving, static_mask, moving_mask)\n+\n+                # Test bin_normalize_static at the boundary\n+                normalized = M.bin_normalize_static(min_int)\n+                assert_almost_equal(normalized, M.padding)\n+                index = M.bin_index(normalized)\n+                assert_equal(index, M.padding)\n+                normalized = M.bin_normalize_static(max_int)\n+                assert_almost_equal(normalized, nbins - M.padding)\n+                index = M.bin_index(normalized)\n+                assert_equal(index, nbins - 1 - M.padding)\n+\n+                # Test bin_normalize_moving at the boundary\n+                normalized = M.bin_normalize_moving(factor * min_int)\n+                assert_almost_equal(normalized, M.padding)\n+                index = M.bin_index(normalized)\n+                assert_equal(index, M.padding)\n+                normalized = M.bin_normalize_moving(factor * max_int)\n+                assert_almost_equal(normalized, nbins - M.padding)\n+                index = M.bin_index(normalized)\n+                assert_equal(index, nbins - 1 - M.padding)\n+\n+                # Test bin_index not at the boundary\n+                delta_s = (max_int - min_int)/(nbins - 2*M.padding)\n+                delta_m = factor * (max_int - min_int)/(nbins - 2*M.padding)\n+                for i in range(nbins - 2*M.padding):\n+                    normalized = M.bin_normalize_static(min_int +\n+                                                        (i+0.5)*delta_s)\n+                    index = M.bin_index(normalized)\n+                    assert_equal(index, M.padding + i)\n+\n+                    normalized = M.bin_normalize_moving(factor*min_int +\n+                                                        (i+0.5)*delta_m)\n+                    index = M.bin_index(normalized)\n+                    assert_equal(index, M.padding + i)\n+\n+\n+def test_mattes_densities_dense():\n+    # Test the computation of the joint intensity distribution\n+    # using a dense set of values (from a pair of images)\n+    np.random.seed(1246592)\n+    nbins = 32\n+    nr = 30\n+    nc = 35\n+    ns = 20\n+    nvals = 50\n+\n+    for dim in [2, 3]:\n+        if dim == 2:\n+            shape = (nr, nc)\n+            static, moving = create_random_image_pair_2d(nr, nc, nvals)\n+        else:\n+            shape = (ns, nr, nc)\n+            static, moving = create_random_image_pair_3d(ns, nr, nc, nvals)\n+\n+        # Initialize\n+        mbase = MattesBase(nbins)\n+        mbase.setup(static, moving)\n+        mbase.update_pdfs_dense(static, moving)\n+        actual_joint = mbase.joint\n+        actual_mmarginal = mbase.mmarginal\n+        actual_smarginal = mbase.smarginal\n+\n+        # Compute the expected joint distribution\n+        expected_joint = np.zeros(shape=(nbins, nbins))\n+        for index in ndindex(shape):\n+            sval = mbase.bin_normalize_static(static[index])\n+            mval = mbase.bin_normalize_moving(moving[index])\n+            sbin = mbase.bin_index(sval)\n+            mbin = mbase.bin_index(mval)\n+            # The spline is centered at mval, will evaluate for all row\n+            spline_arg = np.array([i - mval for i in range(nbins)])\n+            contribution = cubic_spline(spline_arg)\n+            expected_joint[sbin, :] += contribution\n+\n+        # Verify joint distribution\n+        expected_joint /= expected_joint.sum()\n+        assert_array_almost_equal(actual_joint, expected_joint)\n+\n+        # Verify moving marginal\n+        expected_mmarginal = expected_joint.sum(0)\n+        expected_mmarginal /= expected_mmarginal.sum()\n+        assert_array_almost_equal(actual_mmarginal, expected_mmarginal)\n+\n+        # Verivy static marginal\n+        expected_smarginal = expected_joint.sum(1)\n+        expected_smarginal /= expected_smarginal.sum()\n+        assert_array_almost_equal(actual_smarginal, expected_smarginal)\n+\n+\n+def test_mattes_densities_sparse():\n+    # Test the computation of the joint intensity distribution\n+    # from a list of pairs of corresponding intensities\n+    np.random.seed(3147702)\n+    nbins = 32\n+    nr = 30\n+    nc = 35\n+    nvals = 50\n+\n+    shape = (nr, nc)\n+    static, moving = create_random_image_pair_2d(nr, nc, nvals)\n+    sval = static.reshape(-1)\n+    mval = moving.reshape(-1)\n+\n+    # Initialize\n+    mbase = MattesBase(nbins)\n+    mbase.setup(static, moving)\n+    mbase.update_pdfs_sparse(sval, mval)\n+    actual_joint = mbase.joint\n+    actual_mmarginal = mbase.mmarginal\n+    actual_smarginal = mbase.smarginal\n+\n+    # Compute the expected joint distribution\n+    expected_joint = np.zeros(shape=(nbins, nbins))\n+    for index in range(sval.shape[0]):\n+        sv = mbase.bin_normalize_static(sval[index])\n+        mv = mbase.bin_normalize_moving(mval[index])\n+        sbin = mbase.bin_index(sv)\n+        mbin = mbase.bin_index(mv)\n+        # The spline is centered at mval, will evaluate for all row\n+        spline_arg = np.array([i - mv for i in range(nbins)])\n+        contribution = cubic_spline(spline_arg)\n+        expected_joint[sbin, :] += contribution\n+\n+    # Verify joint distribution\n+    expected_joint /= expected_joint.sum()\n+    assert_array_almost_equal(actual_joint, expected_joint)\n+\n+    # Verify moving marginal\n+    expected_mmarginal = expected_joint.sum(0)\n+    expected_mmarginal /= expected_mmarginal.sum()\n+    assert_array_almost_equal(actual_mmarginal, expected_mmarginal)\n+\n+    # Verivy static marginal\n+    expected_smarginal = expected_joint.sum(1)\n+    expected_smarginal /= expected_smarginal.sum()\n+    assert_array_almost_equal(actual_smarginal, expected_smarginal)\n+\n+\n+def setup_random_transform(transform, rfactor, nslices=45, sigma=1):\n+    r\"\"\" Creates a pair of images related to each other by an affine transform\n+\n+    We warp the static image with a random transform so that the\n+    returned ground-truth transform will produce the static image\n+    when applied to the moving image. This will simply stack some copies of\n+    a T1 coronal slice image and add some zero slices up and down to\n+    reduce boundary artefacts when interpolating.\n+\n+    Parameters\n+    ----------\n+    transform: instance of Transform\n+        defines the type of random transformation that will be created\n+    rfactor: float\n+        the factor to multiply the uniform(0,1) random noise that will be\n+        added to the identity parameters to create the random transform\n+    nslices: int\n+        number of slices to be stacked to form the volumes\n+    \"\"\"\n+    dim = 2 if nslices ==1 else 3\n+    if transform.get_dim() !=dim:\n+        raise ValueError(\"Transform and requested volume have different dims.\")\n+    np.random.seed(3147702)\n+    zero_slices = nslices//3\n+\n+    fname = get_data('t1_coronal_slice')\n+    moving_slice = np.load(fname)\n+    moving_slice = moving_slice[40:180, 50:210]\n+\n+    if nslices == 1:\n+        dim = 2\n+        moving = moving_slice\n+        warp_method = vf.warp_2d_affine\n+    else:\n+        dim = 3\n+        warp_method = vf.warp_3d_affine\n+        moving = np.zeros(shape=moving_slice.shape + (nslices,))\n+        moving[..., zero_slices:(2 * zero_slices)] = moving_slice[..., None]\n+\n+    moving = sp.ndimage.filters.gaussian_filter(moving, sigma)\n+\n+    moving_aff = np.eye(dim + 1)\n+    mmask = np.ones_like(moving, dtype=np.int32)\n+\n+    # Create a transform by slightly perturbing the identity parameters\n+    theta = transform.get_identity_parameters()\n+    n = transform.get_number_of_parameters()\n+    theta += np.random.rand(n) * rfactor\n+\n+    T = transform.param_to_matrix(theta)\n+    shape = np.array(moving.shape, dtype=np.int32)\n+    static = np.array(warp_method(moving.astype(np.float32), shape, T))\n+    static = static.astype(np.float64)\n+    static_aff = np.eye(dim + 1)\n+    smask = np.ones_like(static, dtype=np.int32)\n+\n+    return static, moving, static_aff, moving_aff, smask, mmask, T\n+\n+\n+def test_joint_pdf_gradients_dense():\n+    # Compare the analytical and numerical (finite differences) gradient of the\n+    # joint distribution (i.e. derivatives of each histogram cell) w.r.t. the\n+    # transform parameters. Since the histograms are discrete partitions of the\n+    # image intensities, the finite difference approximation is normally not\n+    # very close to the analytical derivatives. Other sources of error are the\n+    # interpolation used when warping the images and the boundary intensities\n+    # introduced when interpolating outside of the image (i.e. some \"zeros\" are\n+    # introduced at the boundary which affect the numerical derivatives but is\n+    # not taken into account by the analytical derivatives). Thus, we need to\n+    # relax the verification. Instead of looking for the analytical and\n+    # numerical gradients to be very close to each other, we will verify that\n+    # they approximately point in the same direction by testing the angle they\n+    # form is close to zero.\n+    h = 1e-4\n+    factors = {('TRANSLATION', 2):2.0,\n+               ('ROTATION', 2):0.1,\n+               ('RIGID', 2):0.1,\n+               ('SCALING', 2):0.01,\n+               ('AFFINE', 2):0.1,\n+               ('TRANSLATION', 3):2.0,\n+               ('ROTATION', 3):0.2,\n+               ('RIGID', 3):0.1,\n+               ('SCALING', 3):0.1,\n+               ('AFFINE', 3):0.1}\n+    for ttype in factors.keys():\n+        dim = ttype[1]\n+        if dim == 2:\n+            nslices = 1\n+            warp_method = vf.warp_2d_affine\n+        else:\n+            nslices = 45\n+            warp_method = vf.warp_3d_affine\n+\n+        transform = regtransforms[ttype]\n+        factor = factors[ttype]\n+        theta = transform.get_identity_parameters()\n+\n+        static, moving, static_aff, moving_aff, smask, mmask, T = \\\n+                        setup_random_transform(transform, factor, nslices, 5.0)\n+        metric = MattesBase(32)\n+        metric.setup(static, moving, smask, mmask)\n+\n+        # Compute the gradient at theta with the implementation under test\n+        T = transform.param_to_matrix(theta)\n+        shape = np.array(static.shape, dtype=np.int32)\n+\n+        warped = warp_method(moving.astype(np.float32), shape, T)\n+        warped = np.array(warped)\n+        metric.update_pdfs_dense(static.astype(np.float64),\n+                                 warped.astype(np.float64))\n+        J0 = np.copy(metric.joint)\n+        # Now we have the joint distribution evaluated at theta\n+        grid_to_space = np.eye(dim + 1)\n+        spacing = np.ones(dim, dtype=np.float64)\n+        mgrad, inside = vf.gradient(moving.astype(np.float32), moving_aff,\n+                            spacing, shape, grid_to_space)\n+        id = transform.get_identity_parameters()\n+        metric.update_gradient_dense(id, transform, static.astype(np.float64),\n+                                     warped.astype(np.float64), grid_to_space,\n+                                     mgrad, smask, mmask)\n+        actual = np.copy(metric.joint_grad)\n+        # Now we have the gradient of the joint distribution w.r.t. the\n+        # transform parameters\n+\n+        # Compute the gradient using finite-diferences\n+        n = transform.get_number_of_parameters()\n+        expected = np.empty_like(actual)\n+        for i in range(n):\n+            dtheta = theta.copy()\n+            dtheta[i] += h\n+            # Update the joint distribution with the warped moving image\n+            T = transform.param_to_matrix(dtheta)\n+            shape = np.array(static.shape, dtype=np.int32)\n+            warped = warp_method(moving.astype(np.float32), shape, T)\n+            warped = np.array(warped)\n+            metric.update_pdfs_dense(static.astype(np.float64),\n+                                     warped.astype(np.float64))\n+            J1 =  np.copy(metric.joint)\n+            expected[...,i] = (J1 - J0) / h\n+\n+        # Dot product and norms of gradients of each joint histogram cell\n+        # i.e. the derivatives of each cell w.r.t. all parameters\n+        P = (expected*actual).sum(2)\n+        enorms = np.sqrt((expected**2).sum(2))\n+        anorms = np.sqrt((actual**2).sum(2))\n+        prodnorms = enorms*anorms\n+        # Cosine of angle between the expected and actual gradients.\n+        # Exclude very small gradients\n+        P[prodnorms > 1e-6] /= (prodnorms[prodnorms > 1e-6])\n+        P[prodnorms <= 1e-6]=0\n+        # Verify that a large proportion of the gradients point almost in\n+        # the same direction. Disregard very small gradients\n+        mean_cosine = P[P!=0].mean()\n+        std_cosine = P[P!=0].std()\n+        assert_equal(mean_cosine > 0.9, True)\n+        assert_equal(std_cosine < 0.25, True)\n+\n+\n+def test_joint_pdf_gradients_sparse():\n+    h = 1e-4\n+    factors = {('TRANSLATION', 2):2.0,\n+               ('ROTATION', 2):0.1,\n+               ('RIGID', 2):0.1,\n+               ('SCALING', 2):0.01,\n+               ('AFFINE', 2):0.1,\n+               ('TRANSLATION', 3):2.0,\n+               ('ROTATION', 3):0.2,\n+               ('RIGID', 3):0.1,\n+               ('SCALING', 3):0.1,\n+               ('AFFINE', 3):0.1}","path":"dipy/align/tests/test_mattes.py","position":null,"original_position":436,"commit_id":"06eff06fefead2b51a2e728266defb46d59cdf0e","original_commit_id":"1f1952c7bf997c19989df36440b428eb9973fcdc","user":{"login":"omarocegueda","id":1608583,"avatar_url":"https://avatars.githubusercontent.com/u/1608583?v=3","gravatar_id":"","url":"https://api.github.com/users/omarocegueda","html_url":"https://github.com/omarocegueda","followers_url":"https://api.github.com/users/omarocegueda/followers","following_url":"https://api.github.com/users/omarocegueda/following{/other_user}","gists_url":"https://api.github.com/users/omarocegueda/gists{/gist_id}","starred_url":"https://api.github.com/users/omarocegueda/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/omarocegueda/subscriptions","organizations_url":"https://api.github.com/users/omarocegueda/orgs","repos_url":"https://api.github.com/users/omarocegueda/repos","events_url":"https://api.github.com/users/omarocegueda/events{/privacy}","received_events_url":"https://api.github.com/users/omarocegueda/received_events","type":"User","site_admin":false},"body":"Moved out as a global dictionary","created_at":"2015-05-04T16:40:09Z","updated_at":"2015-05-04T16:40:09Z","html_url":"https://github.com/nipy/dipy/pull/628#discussion_r29599781","pull_request_url":"https://api.github.com/repos/nipy/dipy/pulls/628","_links":{"self":{"href":"https://api.github.com/repos/nipy/dipy/pulls/comments/29599781"},"html":{"href":"https://github.com/nipy/dipy/pull/628#discussion_r29599781"},"pull_request":{"href":"https://api.github.com/repos/nipy/dipy/pulls/628"}}},"pull_request":{"url":"https://api.github.com/repos/nipy/dipy/pulls/628","id":33130968,"html_url":"https://github.com/nipy/dipy/pull/628","diff_url":"https://github.com/nipy/dipy/pull/628.diff","patch_url":"https://github.com/nipy/dipy/pull/628.patch","issue_url":"https://api.github.com/repos/nipy/dipy/issues/628","number":628,"state":"open","locked":false,"title":"Affine registration PR 2/3","user":{"login":"omarocegueda","id":1608583,"avatar_url":"https://avatars.githubusercontent.com/u/1608583?v=3","gravatar_id":"","url":"https://api.github.com/users/omarocegueda","html_url":"https://github.com/omarocegueda","followers_url":"https://api.github.com/users/omarocegueda/followers","following_url":"https://api.github.com/users/omarocegueda/following{/other_user}","gists_url":"https://api.github.com/users/omarocegueda/gists{/gist_id}","starred_url":"https://api.github.com/users/omarocegueda/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/omarocegueda/subscriptions","organizations_url":"https://api.github.com/users/omarocegueda/orgs","repos_url":"https://api.github.com/users/omarocegueda/repos","events_url":"https://api.github.com/users/omarocegueda/events{/privacy}","received_events_url":"https://api.github.com/users/omarocegueda/received_events","type":"User","site_admin":false},"body":"This is the second PR to add affine registration to the align module. It contains the low level functions to compute the Mattes' Mutual Information metric and its analytic gradient w.r.t. the parameters of the transformations defined in PR1.\r\n","created_at":"2015-04-13T01:39:26Z","updated_at":"2015-05-04T16:40:09Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e4bc4ad4f5f37c71dc9d18396e770c117df12b31","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/nipy/dipy/pulls/628/commits","review_comments_url":"https://api.github.com/repos/nipy/dipy/pulls/628/comments","review_comment_url":"https://api.github.com/repos/nipy/dipy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/nipy/dipy/issues/628/comments","statuses_url":"https://api.github.com/repos/nipy/dipy/statuses/06eff06fefead2b51a2e728266defb46d59cdf0e","head":{"label":"omarocegueda:imaffine_pr2","ref":"imaffine_pr2","sha":"06eff06fefead2b51a2e728266defb46d59cdf0e","user":{"login":"omarocegueda","id":1608583,"avatar_url":"https://avatars.githubusercontent.com/u/1608583?v=3","gravatar_id":"","url":"https://api.github.com/users/omarocegueda","html_url":"https://github.com/omarocegueda","followers_url":"https://api.github.com/users/omarocegueda/followers","following_url":"https://api.github.com/users/omarocegueda/following{/other_user}","gists_url":"https://api.github.com/users/omarocegueda/gists{/gist_id}","starred_url":"https://api.github.com/users/omarocegueda/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/omarocegueda/subscriptions","organizations_url":"https://api.github.com/users/omarocegueda/orgs","repos_url":"https://api.github.com/users/omarocegueda/repos","events_url":"https://api.github.com/users/omarocegueda/events{/privacy}","received_events_url":"https://api.github.com/users/omarocegueda/received_events","type":"User","site_admin":false},"repo":{"id":9781072,"name":"dipy","full_name":"omarocegueda/dipy","owner":{"login":"omarocegueda","id":1608583,"avatar_url":"https://avatars.githubusercontent.com/u/1608583?v=3","gravatar_id":"","url":"https://api.github.com/users/omarocegueda","html_url":"https://github.com/omarocegueda","followers_url":"https://api.github.com/users/omarocegueda/followers","following_url":"https://api.github.com/users/omarocegueda/following{/other_user}","gists_url":"https://api.github.com/users/omarocegueda/gists{/gist_id}","starred_url":"https://api.github.com/users/omarocegueda/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/omarocegueda/subscriptions","organizations_url":"https://api.github.com/users/omarocegueda/orgs","repos_url":"https://api.github.com/users/omarocegueda/repos","events_url":"https://api.github.com/users/omarocegueda/events{/privacy}","received_events_url":"https://api.github.com/users/omarocegueda/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/omarocegueda/dipy","description":"Diffusion MR Imaging in Python","fork":true,"url":"https://api.github.com/repos/omarocegueda/dipy","forks_url":"https://api.github.com/repos/omarocegueda/dipy/forks","keys_url":"https://api.github.com/repos/omarocegueda/dipy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/omarocegueda/dipy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/omarocegueda/dipy/teams","hooks_url":"https://api.github.com/repos/omarocegueda/dipy/hooks","issue_events_url":"https://api.github.com/repos/omarocegueda/dipy/issues/events{/number}","events_url":"https://api.github.com/repos/omarocegueda/dipy/events","assignees_url":"https://api.github.com/repos/omarocegueda/dipy/assignees{/user}","branches_url":"https://api.github.com/repos/omarocegueda/dipy/branches{/branch}","tags_url":"https://api.github.com/repos/omarocegueda/dipy/tags","blobs_url":"https://api.github.com/repos/omarocegueda/dipy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/omarocegueda/dipy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/omarocegueda/dipy/git/refs{/sha}","trees_url":"https://api.github.com/repos/omarocegueda/dipy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/omarocegueda/dipy/statuses/{sha}","languages_url":"https://api.github.com/repos/omarocegueda/dipy/languages","stargazers_url":"https://api.github.com/repos/omarocegueda/dipy/stargazers","contributors_url":"https://api.github.com/repos/omarocegueda/dipy/contributors","subscribers_url":"https://api.github.com/repos/omarocegueda/dipy/subscribers","subscription_url":"https://api.github.com/repos/omarocegueda/dipy/subscription","commits_url":"https://api.github.com/repos/omarocegueda/dipy/commits{/sha}","git_commits_url":"https://api.github.com/repos/omarocegueda/dipy/git/commits{/sha}","comments_url":"https://api.github.com/repos/omarocegueda/dipy/comments{/number}","issue_comment_url":"https://api.github.com/repos/omarocegueda/dipy/issues/comments{/number}","contents_url":"https://api.github.com/repos/omarocegueda/dipy/contents/{+path}","compare_url":"https://api.github.com/repos/omarocegueda/dipy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/omarocegueda/dipy/merges","archive_url":"https://api.github.com/repos/omarocegueda/dipy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/omarocegueda/dipy/downloads","issues_url":"https://api.github.com/repos/omarocegueda/dipy/issues{/number}","pulls_url":"https://api.github.com/repos/omarocegueda/dipy/pulls{/number}","milestones_url":"https://api.github.com/repos/omarocegueda/dipy/milestones{/number}","notifications_url":"https://api.github.com/repos/omarocegueda/dipy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/omarocegueda/dipy/labels{/name}","releases_url":"https://api.github.com/repos/omarocegueda/dipy/releases{/id}","created_at":"2013-04-30T22:05:45Z","updated_at":"2015-04-21T20:08:08Z","pushed_at":"2015-05-04T16:33:05Z","git_url":"git://github.com/omarocegueda/dipy.git","ssh_url":"git@github.com:omarocegueda/dipy.git","clone_url":"https://github.com/omarocegueda/dipy.git","svn_url":"https://github.com/omarocegueda/dipy","homepage":"http://dipy.org","size":17124,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":2,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"base":{"label":"nipy:master","ref":"master","sha":"49cb6e3837360f2928e5ca5f8fcf9afaa94a4c58","user":{"login":"nipy","id":233707,"avatar_url":"https://avatars.githubusercontent.com/u/233707?v=3","gravatar_id":"","url":"https://api.github.com/users/nipy","html_url":"https://github.com/nipy","followers_url":"https://api.github.com/users/nipy/followers","following_url":"https://api.github.com/users/nipy/following{/other_user}","gists_url":"https://api.github.com/users/nipy/gists{/gist_id}","starred_url":"https://api.github.com/users/nipy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nipy/subscriptions","organizations_url":"https://api.github.com/users/nipy/orgs","repos_url":"https://api.github.com/users/nipy/repos","events_url":"https://api.github.com/users/nipy/events{/privacy}","received_events_url":"https://api.github.com/users/nipy/received_events","type":"Organization","site_admin":false},"repo":{"id":505209,"name":"dipy","full_name":"nipy/dipy","owner":{"login":"nipy","id":233707,"avatar_url":"https://avatars.githubusercontent.com/u/233707?v=3","gravatar_id":"","url":"https://api.github.com/users/nipy","html_url":"https://github.com/nipy","followers_url":"https://api.github.com/users/nipy/followers","following_url":"https://api.github.com/users/nipy/following{/other_user}","gists_url":"https://api.github.com/users/nipy/gists{/gist_id}","starred_url":"https://api.github.com/users/nipy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nipy/subscriptions","organizations_url":"https://api.github.com/users/nipy/orgs","repos_url":"https://api.github.com/users/nipy/repos","events_url":"https://api.github.com/users/nipy/events{/privacy}","received_events_url":"https://api.github.com/users/nipy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/nipy/dipy","description":"Diffusion MR Imaging in Python","fork":false,"url":"https://api.github.com/repos/nipy/dipy","forks_url":"https://api.github.com/repos/nipy/dipy/forks","keys_url":"https://api.github.com/repos/nipy/dipy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nipy/dipy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nipy/dipy/teams","hooks_url":"https://api.github.com/repos/nipy/dipy/hooks","issue_events_url":"https://api.github.com/repos/nipy/dipy/issues/events{/number}","events_url":"https://api.github.com/repos/nipy/dipy/events","assignees_url":"https://api.github.com/repos/nipy/dipy/assignees{/user}","branches_url":"https://api.github.com/repos/nipy/dipy/branches{/branch}","tags_url":"https://api.github.com/repos/nipy/dipy/tags","blobs_url":"https://api.github.com/repos/nipy/dipy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nipy/dipy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nipy/dipy/git/refs{/sha}","trees_url":"https://api.github.com/repos/nipy/dipy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nipy/dipy/statuses/{sha}","languages_url":"https://api.github.com/repos/nipy/dipy/languages","stargazers_url":"https://api.github.com/repos/nipy/dipy/stargazers","contributors_url":"https://api.github.com/repos/nipy/dipy/contributors","subscribers_url":"https://api.github.com/repos/nipy/dipy/subscribers","subscription_url":"https://api.github.com/repos/nipy/dipy/subscription","commits_url":"https://api.github.com/repos/nipy/dipy/commits{/sha}","git_commits_url":"https://api.github.com/repos/nipy/dipy/git/commits{/sha}","comments_url":"https://api.github.com/repos/nipy/dipy/comments{/number}","issue_comment_url":"https://api.github.com/repos/nipy/dipy/issues/comments{/number}","contents_url":"https://api.github.com/repos/nipy/dipy/contents/{+path}","compare_url":"https://api.github.com/repos/nipy/dipy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nipy/dipy/merges","archive_url":"https://api.github.com/repos/nipy/dipy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nipy/dipy/downloads","issues_url":"https://api.github.com/repos/nipy/dipy/issues{/number}","pulls_url":"https://api.github.com/repos/nipy/dipy/pulls{/number}","milestones_url":"https://api.github.com/repos/nipy/dipy/milestones{/number}","notifications_url":"https://api.github.com/repos/nipy/dipy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nipy/dipy/labels{/name}","releases_url":"https://api.github.com/repos/nipy/dipy/releases{/id}","created_at":"2010-02-06T11:43:08Z","updated_at":"2015-04-30T05:53:35Z","pushed_at":"2015-05-04T16:33:06Z","git_url":"git://github.com/nipy/dipy.git","ssh_url":"git@github.com:nipy/dipy.git","clone_url":"https://github.com/nipy/dipy.git","svn_url":"https://github.com/nipy/dipy","homepage":"http://dipy.org","size":20519,"stargazers_count":60,"watchers_count":60,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":87,"mirror_url":null,"open_issues_count":71,"forks":87,"open_issues":71,"watchers":60,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/nipy/dipy/pulls/628"},"html":{"href":"https://github.com/nipy/dipy/pull/628"},"issue":{"href":"https://api.github.com/repos/nipy/dipy/issues/628"},"comments":{"href":"https://api.github.com/repos/nipy/dipy/issues/628/comments"},"review_comments":{"href":"https://api.github.com/repos/nipy/dipy/pulls/628/comments"},"review_comment":{"href":"https://api.github.com/repos/nipy/dipy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/nipy/dipy/pulls/628/commits"},"statuses":{"href":"https://api.github.com/repos/nipy/dipy/statuses/06eff06fefead2b51a2e728266defb46d59cdf0e"}}}}