Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crofton perimeter and Euler number #4121

Open
wants to merge 40 commits into
base: main
Choose a base branch
from

Conversation

yg42
Copy link
Contributor

@yg42 yg42 commented Aug 28, 2019

Description

This PR consists in 2 changes in regionprops functions

  1. A new Euler number function is proposed, that uses convolution to get configurations codes, followed by a scalar product. This algorithm is in O(m), with m the total number of pixels in the image. It works for 2D and 3D images. Notice that I propose this method because it seemed that there was an error in the previous version (I compared with the matlab version). I got the coefficients from [1] for 2D images and [2] for 3D images.
    [1] S. Rivollier. Analyse d’image geometrique et morphometrique par diagrammes de forme et voisinages adaptatifs generaux. PhD thesis, 2010. Ecole Nationale Superieure des Mines de Saint-Etienne. https://tel.archives-ouvertes.fr/tel-00560838
    [2] Ohser J., Nagel W., Schladitz K. (2002) The Euler Number of Discretized Sets - On the Choice of Adjacency in Homogeneous Lattices. In: Mecke K., Stoyan D. (eds) Morphology of Condensed Matter. Lecture Notes in Physics, vol 600. Springer, Berlin, Heidelberg

  2. A new perimeter method, based on the Crofton formula [3], is proposed. The same algorithm as for Euler number is used. It works only for 2D images.
    I added an example to illustrate the approximation of the 2 different perimeter methods with 4 and 8 connectivity. The perimeter of a rotated square is represented as a function of the rotation angle.
    [3] https://en.wikipedia.org/wiki/Crofton_formula

Note: There is an interesting link with #3812 and #3797, because minkowski functionals (volume, surface are, integral of mean curvature and Euler number in 3D) can be evaluated by the same method proposed in the previous references (see [4]). You can also refer to
Geometric measure in 2D and 3D for matlab, by D. Legland where a similar method is implemented using a marching cube approach.

[4] Ohser, J., & Schladitz, K. (2009). 3D images of materials structures: processing and analysis. John Wiley & Sons.

Checklist

For reviewers

  • Check that the PR title is short, concise, and will make sense 1 year
    later.
  • Check that new functions are imported in corresponding __init__.py.
  • Check that new features, API changes, and deprecations are mentioned in
    doc/release/release_dev.rst.
  • Consider backporting the PR with @meeseeksdev backport to v0.14.x

Added crofton perimeter approximation for 2D binary images.
Modified Euler number function to get correct values.
Added example to show different perimeters on a rotated square.
Added Euler number evaluation in 3D. Algorithm is on O(m), with m the number of pixels in image.
Euler number uses 8 connectivity by default, 26 in 3D. Modified test in consequence.
Typos in example
@pep8speaks

This comment has been minimized.

mainly removed trailing whitespace and ; at ends of lines
@emmanuelle emmanuelle self-assigned this Sep 9, 2019
@emmanuelle
Copy link
Member

thank you @yg42 ! I'll review your PR in the next couple of days, I'm very excited about having these features for 3D images. Maybe @alexdesiqueira is also an appropriate reviewer.

@emmanuelle
Copy link
Member

So I started playing a bit adapting your example and it's possible that there is a bug somewhere because the result below looks fishy to me:

from skimage.measure import perimeter
from skimage.measure import crofton_perimeter
import numpy as np
from skimage import draw

img = np.zeros((100, 100))
rr, cc = draw.circle_perimeter(50, 50, 20)
img[rr, cc] = 1

print(perimeter(img, 4))
print(perimeter(img, 8))

print(crofton_perimeter(img, 2))
print(crofton_perimeter(img, 4))
----
131.88225099390854
131.88225099390854
251.32741228718345
223.40713078307576

I would not expect the Crofton perimeter to be so different from perimeter... Did I miss something? I'll keep on investigating.

@yg42
Copy link
Contributor Author

yg42 commented Sep 9, 2019

@emmanuelle
My implementation of Crofton perimeter supposes that you have filled objects. If there is a hole, the inner contour is also added in the perimeter value.

Perimeter function only counts the outer contour, is this an expected behavior? I would say no. If I test on a disk I get the same result. My proposition of Crofton perimeter gives approximately the same perimeter for a disk.

from skimage.measure import perimeter
from skimage.measure import crofton_perimeter
import numpy as np
from skimage import draw

img = np.zeros((100, 100))
rr, cc = draw.circle_perimeter(50, 50, 20)
img[rr, cc] = 1
print(perimeter(img, 4))

rr,cc = draw.circle(50, 50, 20);
img[rr, cc] = 1
print(perimeter(img, 4))
crofton_perimeter(img, 4)
----
131.88225099390854
131.88225099390854
127.71373126734748

@emmanuelle
Copy link
Member

Oh yes my example was actually very convoluted :-), sorry about that. I was trying to find an example for which crofton perimeter would get more accurate results than the perimeter function with 4 neighbors, because in the original gallery example you provided, they look really close.

@emmanuelle
Copy link
Member

Would it be possible to explain more the pros and cons of the two algorithms?

skimage/measure/_regionprops.py Outdated Show resolved Hide resolved
skimage/measure/_regionprops.py Outdated Show resolved Hide resolved
Parameters
----------
image: (N, M) ndarray
2D image. If image is not binary, all strictly greater than zero values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all values strictly greater than zero

0, 1, 0, 0, 0, 0, 0, -1, 0]
bins = 16
else: # 3D images
F = np.array([[[0, 0, 0], [0, 0, 0], [0, 0, 0]],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to find a more self-explaining name than F?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to config, as this describes a configuration neighbourhood.

skimage/measure/tests/test_regionprops.py Show resolved Hide resolved
skimage/measure/tests/test_regionprops.py Show resolved Hide resolved
Co-Authored-By: Mark Harfouche <mark.harfouche@gmail.com>
@yg42
Copy link
Contributor Author

yg42 commented Sep 10, 2019

Oh yes my example was actually very convoluted :-), sorry about that. I was trying to find an example for which crofton perimeter would get more accurate results than the perimeter function with 4 neighbors, because in the original gallery example you provided, they look really close.

There is no big difference, which is good!

As you know, the perimeter evaluation is really tricky and there is no absolute method to measure it. You can only have approximations. Crofton perimeter, as well as previously present perimeter function in skimage are one of them. OpenCV has a different approach: it approximates the contour with polygons and evaluates the arc length. In fact, the question of evaluating the perimeter is linked to finding the original boundary of the discretized object. Maybe I will work on this last approach and try to include it in skimage.

In 3D, this is the same problem as surface area evaluation, I know you mentioned it in feature requests.

This is not easy to evaluate the efficiency of a perimeter function, and I wouldnt dare saying that one method is better than the other.

yg42 and others added 2 commits September 10, 2019 16:54
Co-Authored-By: Mark Harfouche <mark.harfouche@gmail.com>
Added explanations
Modified variable name to be explicit
Corrected english in comments
@@ -835,6 +846,133 @@ def regionprops(label_image, intensity_image=None, cache=True):
return regions


def euler_number(image, neighbourhood=8):
"""Calculate the Euler characteristic in 2D binary image, that characterize
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2D or 3D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

"""Calculate the Euler characteristic in 2D binary image, that characterize
the topology of the objects.

A neighbourhood configuration is constructed (see variable config),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove "see variable config" since we don't expect users reading the docstring to read the code as well. However you can move this to a comment within the function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@emmanuelle
Copy link
Member

@yg42 regarding perimeter evaluation based on polygons, we already have the building blocks for this with marching cubes in 3d and measure.find_contours in 2D, but indeed we're not using it in regionprops.

@emmanuelle
Copy link
Member

Out of curiosity, why did you want to have Crofton perimeter in scikit-image? The Euler number in 3D was clearly missing (all the more if the 2D version is buggy), but I have more mixed feelings about adding another perimeter function.

@emmanuelle
Copy link
Member

Thank you very much @yg42. I left a couple of minor comments, besides we also need

  • to decide whether the Crofton perimeter is needed in scikit-image or not (@skimage/core ?)
  • if yes, to flesh out a bit the introduction paragraph of the perimeter gallery example to explain the different between perimeter functions
  • and to add a gallery example with the Euler characteristic.

I know it's a lot of work, please bear with us!

@yg42
Copy link
Contributor Author

yg42 commented Sep 11, 2019

@emmanuelle
Thank you for your comments, I really appreciate working on this proposition. I will work on this until the PR is completed.

About the Crofton perimeter interest, I wanted to make a presentation to my colleagues who work in the field of chemical engineering. As most of the users of skimage (I suppose), they are not aware that a perimeter in discrete objects is only an approximation, and I wanted to illustrate this by simple examples (I added the perimeter evaluation of a rotated square in the examples). For this purpose, I coded the Crofton perimeter. As this code is really similar to the Euler characteristic evaluation, I detected some problems on it and decided to submit these two methods.

In my opinion, Crofton perimeter has to be in the measure submodule. Does it have to be in regionprops? It can create confusion for general users, but it is an interesting property, mathematically elegant, and practically quite precise for high sampling and general objects.

I will soon add explanations and discussion in the gallery example, even if the Crofton perimeter is not included, it can be interesting to warn people about this approximation.

I will propose an example illustrating the Euler number, but I have to think of it.
yann

@emmanuelle
Copy link
Member

Thank you @yg42 ! One thing I forgot: since you're adding new features and the behavior of euler_number changed, this should be mentioned in the release notes doc/release/release_dev.rst

@yg42
Copy link
Contributor Author

yg42 commented Sep 12, 2019

ok, I mentioned it as a bug and as a new feature (the function is now a method and has neighbourhood parameters).
I also modified the description in regionprops in order to describe the behavior for 3D images as well as for label images.

I will wait for a decision about the crofton perimeter.

yg42 and others added 10 commits October 8, 2019 12:13
Change name of function to perimeter_crofton in order to use autocompletion
Connectivity is used instead of neighborhood. the example was not using this parameter.
Added sentence to explain objects and holes
Reduced the number of generated images
change in description of euler number function
@yg42
Copy link
Contributor Author

yg42 commented Oct 17, 2019

I did my best to rebase on trunk. Just tell me if I still have things to do. :)

@yg42
Copy link
Contributor Author

yg42 commented Nov 7, 2019

@emmanuelle
What should I do now ?

@alexdesiqueira
Copy link
Member

@jni @hmaarrfk I get afraid when I see lots of old PRs within one, like here. Should I?
Other than that, LGTM.

@yg42
Copy link
Contributor Author

yg42 commented Nov 13, 2019

@alexdesiqueira @jni @emmanuelle
Is there something to do ?
yann

@jni
Copy link
Member

jni commented Nov 13, 2019

@yg42 apologies, I have had an extremely busy past few weeks. I will try to review this properly next week, if someone doesn't beat me to it!

@soupault
Copy link
Member

soupault commented Nov 13, 2019

I think this is a very exciting PR, but I need a bit more time to read through the theoretical aspects.
The rebase did go wrong, it seems, let's see what we can do.

This was referenced Dec 13, 2019
@rfezzani rfezzani added 🩹 type: Bug fix Fixes unexpected or incorrect behavior and removed type: bug labels Feb 22, 2020
@yg42
Copy link
Contributor Author

yg42 commented Jun 10, 2020

Hi all,
Its been a long time since this discussion started, I am not sure what needs to be done. I am kind of busy these days, but if needed, I could relaunch this PR.
all the best
yann

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🩹 type: Bug fix Fixes unexpected or incorrect behavior 🙏 Feature request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants