Repository: scikit-image/scikit-image

Issue:
Improper computation of `centroid` in `measure._moments`
I noticed that the test suite `test_moments.py` did not properly cover cases where `center=None` (its default) in `moments_central`. As the adjusted test failed, I decided to create a test for `centroid` (called to compute the center of the image when none was provided) itself.

It failed:
```python
======================================== FAILURES =========================================
______________________________________ test_centroid ______________________________________

    def test_centroid():
        image = np.zeros((20, 20), dtype=np.double)
        image[14, 14] = 1
        image[15, 15] = 1
        image[14, 15] = 0.5
        image[15, 14] = 0.5
        im_centroid = centroid(image)
>       assert_equal(im_centroid, (14.5, 14.5))
E       AssertionError: 
E       Arrays are not equal
E       
E       (mismatch 100.0%)
E        x: array([ 43.5,  43.5])
E        y: array([ 14.5,  14.5])

skimage/measure/tests/test_moments.py:141: AssertionError
```

This bug was introduced in https://github.com/scikit-image/scikit-image/commit/a346268a461ef2a403ed8e8da6e9e446c741e75b.


Discussion:

Resolving patch:
diff --git a/skimage/measure/_moments.py b/skimage/measure/_moments.py
--- a/skimage/measure/_moments.py
+++ b/skimage/measure/_moments.py
@@ -235,7 +235,9 @@ def centroid(image):
         The centroid of the (nonzero) pixels in ``image``.
     """
     M = moments_central(image, center=(0,) * image.ndim, order=1)
-    center = M[tuple(np.eye(image.ndim, dtype=int))]
+    center = (M[tuple(np.eye(image.ndim, dtype=int))]  # array of weighted sums
+                                                       # for each axis
+              / M[(0,) * image.ndim])  # weighted sum of all points
     return center
 
 


Test patch:
diff --git a/skimage/measure/tests/test_moments.py b/skimage/measure/tests/test_moments.py
--- a/skimage/measure/tests/test_moments.py
+++ b/skimage/measure/tests/test_moments.py
@@ -1,10 +1,12 @@
+from __future__ import division
 import numpy as np
 from skimage import draw
 from skimage.measure import (moments, moments_central, moments_normalized,
-                             moments_hu)
+                             moments_hu, centroid)
 
 from skimage._shared import testing
-from skimage._shared.testing import assert_equal, assert_almost_equal
+from skimage._shared.testing import (assert_equal, assert_almost_equal,
+                                     assert_allclose)
 from skimage._shared._warnings import expected_warnings
 
 
@@ -28,6 +30,10 @@ def test_moments_central():
     image[15, 14] = 0.5
     mu = moments_central(image, (14.5, 14.5))
 
+    # check for proper centroid computation
+    mu_calc_centroid = moments_central(image)
+    assert_equal(mu, mu_calc_centroid)
+
     # shift image by dx=2, dy=2
     image2 = np.zeros((20, 20), dtype=np.double)
     image2[16, 16] = 1
@@ -96,3 +102,11 @@ def test_moments_hu():
     hu2 = moments_hu(nu2)
     # central moments must be translation and scale invariant
     assert_almost_equal(hu, hu2, decimal=1)
+
+
+def test_centroid():
+    image = np.zeros((20, 20), dtype=np.double)
+    image[14, 14:16] = 1
+    image[15, 14:16] = 1/3
+    image_centroid = centroid(image)
+    assert_allclose(image_centroid, (14.25, 14.5))