scikit-image icon indicating copy to clipboard operation
scikit-image copied to clipboard

threshold_li gets stuck in infinite loop due to value oscillation

Open janniswi00 opened this issue 8 months ago • 5 comments

Description:

When applying skimage.filters.threshold_li to a specific float32 image, the algorithm enters an infinite loop. The threshold value oscillates between two close values and never converges.

This happens in a scientific image processing pipeline, even without saving/loading from file. I saved the problematic image directly to verify this behavior.

Expected Behavior: threshold_li should converge to a stable threshold value or raise an error if convergence is not possible.

Actual Behavior: The threshold alternates indefinitely between two very close float values (0.018132983 ↔ 0.018132977) without satisfying the convergence condition.

This behavior also appears in scikit-image version 0.25.2. In specific cases this problem can be solved by manually setting the tolerance.

density_map_bug.zip

Way to reproduce:

import numpy as np
from skimage.filters import threshold_li
import zipfile

with zipfile.ZipFile("density_map_bug.zip", 'r') as zip_ref:
    zip_ref.extractall()

density_map = np.load("density_map_bug.npy")
threshold_li(density_map, iter_callback=print) 

Version information:

3.9.21 (main, Dec 11 2024, 16:24:11) 
[GCC 11.2.0]
Linux-6.8.0-58-generic-x86_64-with-glibc2.39
scikit-image version: 0.24.0
numpy version: 1.26.4

janniswi00 avatar May 08 '25 10:05 janniswi00

Yikes. That's amazing. 😂 Thanks for the report @janniswi00!

My first instinct is that we could check against seen values, or maybe even just the most recent value, and then terminate.

I don't know whether this is true but my intuition is that you couldn't have longer loops than two values, so checking against the last seen value is probably sufficient.

jni avatar May 08 '25 14:05 jni

Yes, my intuition is the same, there shouldn't be a loop with more than two values.

janniswi00 avatar May 10 '25 21:05 janniswi00

Hey @janniswi00 thanks for the report. Could I maybe ask you to simplify to a reproducing snippet or attach the image in a nonbinary format instead? I feel somewhat uncomfortable downloading and opening an "untrusted" ZIP.

Ideally, this would be a few lines of codes that reproduce the buggy scenario. We'd have to do that anyway to add a regression test.

You could also try a simple numpy.savetxt or if that doesn't work a maybe a GIF?

import imageio.v3 as iio  # Dependency of skimage
iio.imwrite("density_map_bug.gif", image)

lagru avatar May 15 '25 12:05 lagru

Hi @lagru, I can upload the image as txt.gz file. The txt-file is too big and the bug is not reproducible with GIF.

density_map.txt.gz

import numpy as np
import skimage
dm = np.loadtxt("density_map.txt.gz", dtype='float32')
skimage.filters.threshold_li(dm, iter_callback=print)

janniswi00 avatar May 20 '25 08:05 janniswi00