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
filters.rank.mean_percentile gives unexpected zeros #7096
Comments
Hey @ryandeon, thanks for the report and effort with the minimal example!
I don't quite follow this statement? Could you maybe elaborate? I think the kernel function that selects the pixels from the local neighborhood is this one scikit-image/skimage/filters/rank/percentile_cy.pyx Lines 73 to 97 in 06b7986
When I put a print statement in there to debug, it looks like If you provide me with the original image I can have a look at how the kernel behaves there as well. Or you can try out this patch (or similar yourself): Subject: [PATCH] Debug _kernel_mean with print
---
Index: skimage/filters/rank/percentile_cy.pyx
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/skimage/filters/rank/percentile_cy.pyx b/skimage/filters/rank/percentile_cy.pyx
--- a/skimage/filters/rank/percentile_cy.pyx (revision 06b7986cceb4c678f63fe3b0b0ab033e21c00f6f)
+++ b/skimage/filters/rank/percentile_cy.pyx (date 1692140252761)
@@ -3,6 +3,7 @@
#cython: nonecheck=False
#cython: wraparound=False
+import numpy as np
cimport numpy as cnp
from .core_cy cimport dtype_t, dtype_t_out, _core, _min, _max
cnp.import_array()
@@ -79,6 +80,9 @@
cdef Py_ssize_t i, sum, mean, n
+ with gil:
+ print(np.array(histo))
+
if pop:
sum = 0
mean = 0
@@ -86,10 +90,18 @@
for i in range(n_bins):
sum += histo[i]
if (sum >= p0 * pop) and (sum <= p1 * pop):
+ with gil:
+ print(" pop, sum, p0, p1", pop, sum, p0, p1, end=" | ")
+ print("adding bin to mean", i, histo[i])
n += histo[i]
mean += histo[i] * i
+ with gil:
+ if histo[i] * i != 0:
+ print(i, histo[i] * i)
if n > 0:
+ with gil:
+ print("assigning mean", mean, n)
out[0] = <dtype_t_out>(mean / n)
else:
out[0] = <dtype_t_out>0
|
Hi @lagru What I expect, and maybe you can determine if it's reasonable, is instead for it add fractional amounts of the histogram when needed. maybe something like for i in range(n_bins):
sum += histo[i]
if sum-histo[i] < p0*pop and sum > p0*pop: # low fractional case
n = histo[i] - int(p0*pop) # not sure how you would want to treat the edges . round up, round down...
mean += n* i
elif (sum >= p0 * pop) and (sum <= p1 * pop): # same as existing code
n += histo[i]
mean += histo[i] * i
elif sum - histo[i] < p1*pop and sum > p1*pop: # high fractional case
n+= int(p1*pop -sum)
mean += int(p1*pop -sum) * i but again, not that I'm used to cython, or have tested this (or optimized it!) last thing : my original images are included in my first post, for some reason the 'input image' is the second one, and the 'output image' is the first (though I intended to upload them in the correct order). There are two black regions in the output that don't seem to belong. |
update: I tried to build skimage from source so I could play around with this, but haven't gotten it working yet. It'll be slow, but I'll keep trying when I get a chance. #found here: https://stackoverflow.com/a/27745627
if (invalid:=(output_img ==0)).any():
ind = ndi.distance_transform_edt(invalid, return_distances=False, return_indices=True)
output_img = output_img [tuple(ind)] |
Nice, let us know if we can help you with setting up the dev environment. :) |
I think I could use some help getting the project built. First time using cython, spin, meson...I'm finding the error messages unhelpful. This thread did not seem like the right place to start troubleshooting build issues, so I found the Zulip and tried there. |
I am still not clear on what behavior to expect from the algorithm. We should get that clear before discussing the implementation. Intuitively I would expect the following:
So if the neighborhood is In that case I'd say there is a bug in the current implementation. However,
So I am not sure at all, whether the current behavior aligns with the original intention of the authors. I am invoking @scikit-image/core in the hope for some institutional knowledge. 🙏 |
Ah, I think I get your suggested implementation in #7096 (comment) now. The problem is, when a portion of a bin should be thrown away and a portion should be factored into the mean. The current implementation just throws away the entire bin. Your suggestion accounts for these cases. 👍 Diff for later
Subject: [PATCH] Fix and optimize _kernel_mean
---
Index: skimage/filters/rank/percentile_cy.pyx
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/skimage/filters/rank/percentile_cy.pyx b/skimage/filters/rank/percentile_cy.pyx
--- a/skimage/filters/rank/percentile_cy.pyx (revision 5408455469ddf3bc9456a6787f903b69a3a52316)
+++ b/skimage/filters/rank/percentile_cy.pyx (date 1693157754527)
@@ -5,6 +5,7 @@
cimport numpy as cnp
from .core_cy cimport dtype_t, dtype_t_out, _core, _min, _max
+from libc.math cimport round, floor, ceil
cnp.import_array()
cdef inline void _kernel_autolevel(dtype_t_out* out, Py_ssize_t odepth,
@@ -77,22 +78,20 @@
cnp.float64_t p0, cnp.float64_t p1,
Py_ssize_t s0, Py_ssize_t s1) noexcept nogil:
- cdef Py_ssize_t i, sum, mean, n
+ cdef Py_ssize_t i, _
+ cdef Py_ssize_t mean = 0
+ cdef Py_ssize_t percentile = 0
+ cdef Py_ssize_t lower = <Py_ssize_t>floor(pop * p0)
+ cdef Py_ssize_t upper = <Py_ssize_t>ceil(pop * p1)
+ cdef Py_ssize_t n = upper - lower
- if pop:
- sum = 0
- mean = 0
- n = 0
+ if pop and n:
for i in range(n_bins):
- sum += histo[i]
- if (sum >= p0 * pop) and (sum <= p1 * pop):
- n += histo[i]
- mean += histo[i] * i
-
- if n > 0:
- out[0] = <dtype_t_out>(mean / n)
- else:
- out[0] = <dtype_t_out>0
+ for _ in range(histo[i]):
+ percentile += 1
+ if lower < percentile < upper:
+ mean += i
+ out[0] = <dtype_t_out>round(mean / n)
else:
out[0] = <dtype_t_out>0 |
See #7111 for an attempted fix. This is the output I get for your provided example image with e5e62bb: import numpy as np
import skimage as ski
import matplotlib.pyplot as plt
#actual case
input_img = ski.io.imread("~/Temp/input.png")
output_img = ski.filters.rank.mean_percentile(
input_img,
footprint=ski.morphology.disk(min(input_img.shape)//4),
p0=0.25,
p1=0.75
)
fig,ax = plt.subplots(1,2,sharex='all',sharey='all')
ax[0].imshow(input_img)
ax[1].imshow(output_img)
#Minimal test case
test = np.hstack([np.ones((5,5)),np.zeros((5,5))])
test_out = ski.filters.rank.mean_percentile(
test,
footprint=ski.morphology.disk(2),
p0=0.25,
p1=0.75
)
print(f"output max values is {np.max(test_out)}, but it should contain some higher values!")
fig.show()
# output max values is 255, but it should contain some higher values! How does this look to you? |
Hi, thanks for pressing on with this! That is looking very good to me. I haven't spent any more time looking at the back end of this, but I do appreciate seeing the steps and fixes you're detailing, it'll help my understanding. |
Hello scikit-image core devs! There hasn't been any activity on this issue for more than 180 days. I have marked it as "dormant" to make it easy to find. |
Description:
When running mean_percentile, I get regions where the input image is completely made up of pixel values > 0, but the output image is set to 0.
I'm expecting this to take the pixels within the footprint, throw away pixel value entries below P0 and above P1, and return the mean. This should be >0, in this case.
Is is possible that the local population could be completely thrown away, leaving nothing? If the region is very flat, it could have very few distinct pixel values. If all copies of values determined to be > P1 are removed, instead of just the top x% of copies, that might result in the behaviour I see? just guessing.
The test case included seems consistent with that, but I'm not confident it's the same in my real image case.
Way to reproduce:
Version information:
The text was updated successfully, but these errors were encountered: