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

h_maxima/minima strange behaviors on floating point image input #4194

Open
w-hc opened this issue Sep 29, 2019 · 7 comments

Comments

@w-hc
Copy link

@w-hc w-hc commented Sep 29, 2019

Description

I have been trying to use the h_maxima implementation but this function seems to exhibit unstable behaviors. For example,

  1. when the input is all zeros, h_maxima returns a marker filled with all 1s. This seems incorrect.

  2. Also, when my input consists of floating point values, and h set to a floating point threshold, even with a slight 0.1 change in h value, the number of detected instances can vary quite a bit.

Way to reproduce

img = np.zeros((10, 10), dtype=float)
marker = h_maxima(img, h=1)
print(np.unique(marker))

Version information

# Paste the output of the following python commands
from __future__ import print_function
import sys; print(sys.version)
import platform; print(platform.platform())
import skimage; print("scikit-image version: {}".format(skimage.__version__))
import numpy; print("numpy version: {}".format(numpy.__version__))
3.6.8 |Anaconda custom (64-bit)| (default, Dec 30 2018, 01:22:34)
[GCC 7.3.0]
Linux-3.10.0-957.5.1.el7.x86_64-x86_64-with-centos-7.6.1810-Core
scikit-image version: 0.15.0
numpy version: 1.16.4
@jni

This comment has been minimized.

Copy link
Contributor

@jni jni commented Sep 30, 2019

@w-hc thanks for the report!

I don't have time to investigate in detail right now, but

  1. It's unclear to me whether this is a bug or an ambiguous case... I'll have to think more about it.
  2. Without more detail, that is, a specific test image and some code to that shows the maxima "varying quite a bit", we won't be able to help you in this case. It's quite probably that the output is correct for your image. The only guarantee that you can have for h_maxima is that the number of detected maxima should increase with decreasing h.
@w-hc

This comment has been minimized.

Copy link
Author

@w-hc w-hc commented Sep 30, 2019

Here is script and sample image that can reproduce what I see. You need to uncompress this zip file to get the numpy array.

import numpy as np
from skimage.morphology import h_maxima
from scipy import ndimage as ndi
heatmap = np.load('heatmap.npy')
assert heatmap.dtype == np.float32
for h in np.linspace(0.2, 4.0, num=10):
    num_instances = ndi.label(h_maxima(heatmap, h))[1]
    print('at h= {:.3f}, discovered {} instances'.format(h, num_instances))

Output:

at h= 0.200, discovered 17 instances
at h= 0.622, discovered 25 instances
at h= 1.044, discovered 17 instances
at h= 1.467, discovered 25 instances
at h= 1.889, discovered 17 instances
at h= 2.311, discovered 24 instances
at h= 2.733, discovered 16 instances
at h= 3.156, discovered 23 instances
at h= 3.578, discovered 21 instances
at h= 4.000, discovered 23 instances

The correct number of local maxima should be 25.

heatmap.npy.zip

Here is what this floating array looks like when it is plotted with plt.imshow
Screen Shot 2019-09-30 at 12 58 26

@jni

This comment has been minimized.

Copy link
Contributor

@jni jni commented Oct 2, 2019

@w-hc

The correct number of local maxima should be 25.

well then just use 0.622, what's the problem??? 😂

Kidding of course. Yikes! Either I misunderstand h_maxima or this is indeed a significant bug. Thank you so much for the report and for the test case!

@jni

This comment has been minimized.

Copy link
Contributor

@jni jni commented Oct 2, 2019

Yep, definitely a bug. By computing a bunch of results, displaying them in napari, and slicing through them, I can see individiual maxima blinking on and off, instead of being off and staying off:

import numpy as np
from skimage.morphology import h_maxima as hmaxima
import napari

image = np.load('heatmap.npy')
# use this in ipython --gui=qt, or in a `with napari.gui_qt():` code block
viewer = napari.view_image(image, name='original', colormap='green', blending='additive')
result = np.array([hmaxima(image, f) for f in np.linspace(0.2, 100.0, num=100)])
res_layer = viewer.add_image(result, name='result', colormap='magenta', blending='additive')
@jni

This comment has been minimized.

Copy link
Contributor

@jni jni commented Oct 2, 2019

I suspect that this has to do with the handling of the resolution variable here:

resolution = 2 * np.finfo(image.dtype).resolution
if h < resolution:
h = resolution
h_corrected = h - resolution / 2.0
shifted_img = image - h

One concerning thing is that if the image is float32, which it is here, resolution / 2 gets cast to float64, and h - resolution / 2 is float64, while image - h is float32. I don't know whether this will then cause problems but it's plausible that it would.

@w-hc

This comment has been minimized.

Copy link
Author

@w-hc w-hc commented Oct 2, 2019

I tried changing the heatmap dtype to float64 but the error persists, albeit with different number of maxima.

import numpy as np
from skimage.morphology import h_maxima
from scipy import ndimage as ndi
heatmap = np.load('heatmap.npy').astype(np.float64)
assert heatmap.dtype == np.float64
for h in np.linspace(0.2, 4.0, num=10):
    num_instances = ndi.label(h_maxima(heatmap, h))[1]
    print('at h= {:.3f}, discovered {} instances'.format(h, num_instances))
at h= 0.200, discovered 25 instances
at h= 0.622, discovered 12 instances
at h= 1.044, discovered 25 instances
at h= 1.467, discovered 21 instances
at h= 1.889, discovered 11 instances
at h= 2.311, discovered 14 instances
at h= 2.733, discovered 23 instances
at h= 3.156, discovered 16 instances
at h= 3.578, discovered 17 instances
at h= 4.000, discovered 23 instances
@w-hc

This comment has been minimized.

Copy link
Author

@w-hc w-hc commented Oct 2, 2019

Just to confirm, the intensity parameter in peak_local_max works differently than h in h_maxima, right?

I have been trying to understand the exact meaning of h_maxima and find the doc in skimage quite difficult to parse.

A local maximum M of height h is a local maximum for which there is at least one path joining M with a higher maximum on which the minimal value is f(M) - h (i.e. the values along the path are not decreasing by more than h with respect to the maximum’s value) and no path for which the minimal value is greater.

After a bit of research, my current understanding is that h_maxima is equivalent to:

  1. Finding all morphological local maxima
  2. Throw away those maxima whose 'height' <= h
    where 'height' is defined as how much you can monotonically descend from this local peak without rising up again.

It is implemented by morphological reconstruction but in effect it is equivalent to what I described above. (disregarding the fact that the heights of remaining maxima would decrement by h)

However, this understanding seems quite different from what the wordings of the doc imply.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.