Repository: scikit-image/scikit-image

Issue:
skimage.filters.threshold_triangle of empty image -> ValueError: attempt to get argmax of an empty sequence
### Description:

Pass an uniform image to `skimage.filters.threshold_triangle`. This raises an exception with an unclear error message.

It should probably return the uniform value as the threshold, or raise an exception with a clearer error message.

`threshold_isodata`, `threshold_li`, `threshold_local`, `threshold_mean`, `threshold_niblack`, `threshold_otsu`, `threshold_sauvola`, `threshold_yen` have no problem with an empty / uniform image.

(`threshold_minimum` also fails: `RuntimeError: Unable to find two maxima in histogram`)

(`threshold_multiotsu` also fails: `ValueError: After discretization into bins, the input image has only 1 different values. It cannot be thresholded in 3 classes. If there are more unique values before discretization, try increasing the number of bins (`nbins`).`)


### Way to reproduce:

```python
from skimage.filters import threshold_triangle
threshold_triangle(numpy.zeros((10, 10), dtype='uint8'))
```

```
Traceback (most recent call last):
  File "<pyshell#24>", line 1, in <module>
    threshold_triangle(numpy.zeros((10, 10), dtype='uint8'))
  File "C:\Python311\Lib\site-packages\skimage\filters\thresholding.py", line 967, in threshold_triangle
    arg_level = np.argmax(length) + arg_low_level
  File "<__array_function__ internals>", line 200, in argmax
  File "C:\Python311\Lib\site-packages\numpy\core\fromnumeric.py", line 1242, in argmax
    return _wrapfunc(a, 'argmax', axis=axis, out=out, **kwds)
  File "C:\Python311\Lib\site-packages\numpy\core\fromnumeric.py", line 57, in _wrapfunc
    return bound(*args, **kwds)
ValueError: attempt to get argmax of an empty sequence
```

### Version information:

```Shell
3.11.0 (main, Oct 24 2022, 18:26:48) [MSC v.1933 64 bit (AMD64)]
Windows-10-10.0.19045-SP0
scikit-image version: 0.21.0
numpy version: 1.24.3
```
```



Discussion:
Hi @petsuter Thanks for reporting this. I can reproduce. 
Raising a `ValueError` if `len(hist) < 2` or return the uniform value appears to be a fix. Do you agree?
Hi @decorouz
Thanks for reproducing. I agree returning the uniform value would be a useful fix, and also consistent with other thresholding methods in the same file. [Example](https://github.com/scikit-image/scikit-image/blob/v0.21.0/skimage/filters/thresholding.py#L507)

Even better (but not strictly required to fix the bug) would be to reuse the same `_validate_image_histogram` helper and also add an optional `hist` input as in the other methods, which is more general and useful.
Hi @petsuter
Your suggestions are great. If you are feeling inspired and have time, you should create a PR for a fix. I think it would be appropriate to update `test_triangle_uint_images` too?

Resolving patch:
diff --git a/skimage/filters/thresholding.py b/skimage/filters/thresholding.py
--- a/skimage/filters/thresholding.py
+++ b/skimage/filters/thresholding.py
@@ -938,6 +938,10 @@ def threshold_triangle(image, nbins=256):
     peak_height = hist[arg_peak_height]
     arg_low_level, arg_high_level = np.flatnonzero(hist)[[0, -1]]
 
+    if arg_low_level == arg_high_level:
+        # Image has constant intensity.
+        return image.ravel()[0]
+
     # Flip is True if left tail is shorter.
     flip = arg_peak_height - arg_low_level < arg_high_level - arg_peak_height
     if flip:


Test patch:
diff --git a/skimage/filters/tests/test_thresholding.py b/skimage/filters/tests/test_thresholding.py
--- a/skimage/filters/tests/test_thresholding.py
+++ b/skimage/filters/tests/test_thresholding.py
@@ -568,6 +568,11 @@ def test_mean():
     img[:, 4:] = 2
     assert(threshold_mean(img) == 1.)
 
+@pytest.mark.parametrize("dtype", [np.uint8, np.int16, np.float16, np.float32])
+def test_triangle_uniform_images(dtype):
+    assert threshold_triangle(np.zeros((10, 10), dtype=dtype)) == 0
+    assert threshold_triangle(np.ones((10, 10), dtype=dtype)) == 1
+    assert threshold_triangle(np.full((10, 10), 2, dtype=dtype)) == 2
 
 def test_triangle_uint_images():
     assert(threshold_triangle(np.invert(data.text())) == 151)