Repository: scikit-image/scikit-image

Issue:
Euler characteristic test for regionprops
In `measure/tests/test_regionprops.py`, `test_euler_number` computes the Euler characteristic of 

```
SAMPLE = np.array(
    [[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
     [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
     [1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0],
     [0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1],
     [0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]])
```

Since the computation of the Euler characteristic uses `ndimage.binary_fill_holes` with 8-connectivity, it only counts holes that are not diagonal-connected to the background. Therefore, the Euler characteristic should be 1, I think (like a sphere), but the test is that it should be 0. 



Discussion:
I wonder whether scikit-image should take over `intvol.pyx` from nipy?

https://github.com/nipy/nipy/blob/master/nipy/algorithms/statistics/intvol.pyx

It contains - for example - the 2D and 3D Euler characteristic calculations:

https://github.com/nipy/nipy/blob/master/nipy/algorithms/statistics/intvol.pyx#L907

https://github.com/nipy/nipy/blob/master/nipy/algorithms/statistics/intvol.pyx#L340

and is written by Jonathan Taylor, co-author of : https://openlibrary.org/books/OL9382828M/Random_Fields_and_Geometry_(Springer_Monographs_in_Mathematics)

Thank you Matthew! It's certainly interesting to know that there exists BSD code for Euler characteristic written by a specialist like Jonathan Taylor. We might need someone more knowledgeable about Euler characteristic to join the discussion: `scikit-image` code implements an intuitive definition of the Euler characteristic  (counting the number of holes as defined by a structuring element), that might differ from the result of the definition used in polyhedral combinatorics. Not sure that this is a problem, but this should be at least documented. Or we could have the two implementations, depending on accuracy and performance.

Hey @emmanuelle, can you elaborate a bit more about what you think is going wrong here? Are we talking about the 0 at [7, 7]? And if so, does the connectivity refer to the 1s or to the 0s? There is a duality between the connectivity of the foreground and that of the background. (ie for 2D, when we use 1-connected background, we get 2-connected foreground, and vice versa.)

(Having said that, I'm certain not "someone more knowledgeable about Euler characteristic". =P And the wikipedia page is bloody useless.)

Back when I implemented regionprops, I followed the conventions in Matlab. It's some time ago, so I cannot really say anything more than that. But this seems to be an issue of definition: what do you consider an object (e.g., individual background regions are separate objects or not).

@jni what puzzled me most was that when I used regionprops to compute the Euler characteristic of the test image I found a value different from the value of the test. I _think_ it did not produce an error because of the context `with expected_warnings(['`background`|CObject type'])` https://github.com/scikit-image/scikit-image/blob/master/skimage/measure/tests/test_regionprops.py#L129 (using nosetests led to an error, but not make test. Go figure...).

Then, about the way Euler characteristic should be computed, I think it depends on how you define the surface of a binary object. 

@jni, yes, with 8-connectivity of the background, the 0 at [7, 7] is not a hole (that would be filled in `filled_image`  https://github.com/scikit-image/scikit-image/blob/master/skimage/measure/_regionprops.py#L175 )


Resolving patch:
diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py
--- a/skimage/measure/_regionprops.py
+++ b/skimage/measure/_regionprops.py
@@ -125,7 +125,7 @@ def equivalent_diameter(self):
     def euler_number(self):
         euler_array = self.filled_image != self.image
         _, num = label(euler_array, neighbors=8, return_num=True,
-                       background=-1)
+                       background=0)
         return -num + 1
 
     def extent(self):
@@ -340,7 +340,7 @@ def regionprops(label_image, intensity_image=None, cache=True):
     **equivalent_diameter** : float
         The diameter of a circle with the same area as the region.
     **euler_number** : int
-        Euler number of region. Computed as number of objects (= 1)
+        Euler characteristic of region. Computed as number of objects (= 1)
         subtracted by number of holes (8-connectivity).
     **extent** : float
         Ratio of pixels in the region to pixels in the total bounding box.


Test patch:
diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py
--- a/skimage/measure/tests/test_regionprops.py
+++ b/skimage/measure/tests/test_regionprops.py
@@ -129,12 +129,12 @@ def test_equiv_diameter():
 
 def test_euler_number():
     en = regionprops(SAMPLE)[0].euler_number
-    assert en == 0
+    assert en == 1
 
     SAMPLE_mod = SAMPLE.copy()
     SAMPLE_mod[7, -3] = 0
     en = regionprops(SAMPLE_mod)[0].euler_number
-    assert en == -1
+    assert en == 0
 
 
 def test_extent():