Repository: scikit-image/scikit-image

Issue:
Convex Hull - dependent features can trip up regionprops calculation
## Description

For some degenerate objects (e.g. intrinisically one less dimension, too few voxels), convex hull calculation throws exceptions.
This has been reported by Yi (@sunyi000 on Github) here 
 https://forum.image.sc/t/scikit-image-regionprops-and-regionprops-table-questions/52021/2
for intrinsically 1d objects. In this scase scipy throws the following exception. @jni has also been part of that image.sc thread.

```
scipy.spatial.qhull.QhullError: QH6013 qhull input error: input is less than 3-dimensional since all points have the same x coordinate    0
```
Other objects that can cause such exceptions are those with too few points, see for example:
https://github.com/scikit-image/scikit-image/issues/4865#issuecomment-711069382

## Way to reproduce
Still working on a minimal reproducible example

## Suggested way to fix

IMO,
rather than having `measure.regionprops` fail for such object, `QHullErrors` should be caught and the affected label should have a `NaN` in the relevant features. A warning should also be issued if this occurs.


Discussion:
>  rather than having measure.regionprops fail for such object, QHullErrors should be caught and the affected label should have a NaN in the relevant features. A warning should also be issued if this occurs.

In general, this is a good idea. However in some cases (e.g. three colinear points), the convex hull image should simply be the same as the input image... What do you think @VolkerH ?
I think so. IMO that is really a bug in `qhull`, but on the other hand I am not firm enough on the mathematical definitions to say  with confidence what the correct output is for these degenerate cased.  In any case, that is something that would have to be fixed upstream, but this isn't even in scipy but some external library.

So for the time being I would suggest adding a try/except around
https://github.com/scikit-image/scikit-image/blob/178e05f17855859d6661ebb6e04d8ddcb50c1cc5/skimage/measure/_regionprops.py#L315
and catch `scipy.spatial.qhull.QhullError`
Just not sure whether the convex image in that case should be an image of `NaN`s and how that would propagate through the other features dependent on the convex image.
Or do that in 
`morphology.convex_hull.convex_hull_image`

Resolving patch:
diff --git a/skimage/morphology/convex_hull.py b/skimage/morphology/convex_hull.py
--- a/skimage/morphology/convex_hull.py
+++ b/skimage/morphology/convex_hull.py
@@ -8,6 +8,13 @@
 from ..util import unique_rows
 from .._shared.utils import warn
 
+try:
+    # Should be public API of scipy spatial once #15003 is released
+    # see https://github.com/scipy/scipy/pull/15003
+    from scipy.spatial import QhullError
+except ImportError:
+    from scipy.spatial.qhull import QhullError
+
 __all__ = ['convex_hull_image', 'convex_hull_object']
 
 
@@ -115,7 +122,13 @@ def convex_hull_image(image, offset_coordinates=True, tolerance=1e-10):
             # when offsetting, we multiply number of vertices by 2 * ndim.
             # therefore, we reduce the number of coordinates by using a
             # convex hull on the original set, before offsetting.
-            hull0 = ConvexHull(coords)
+            try:
+                hull0 = ConvexHull(coords)
+            except QhullError as err:
+                warn(f"Failed to get convex hull image. "
+                     f"Returning empty image, see error message below:\n"
+                     f"{err}")
+                return np.zeros(image.shape, dtype=bool)
             coords = hull0.points[hull0.vertices]
 
     # Add a vertex for the middle of each pixel edge
@@ -128,7 +141,13 @@ def convex_hull_image(image, offset_coordinates=True, tolerance=1e-10):
     coords = unique_rows(coords)
 
     # Find the convex hull
-    hull = ConvexHull(coords)
+    try:
+        hull = ConvexHull(coords)
+    except QhullError as err:
+        warn(f"Failed to get convex hull image. "
+             f"Returning empty image, see error message below:\n"
+             f"{err}")
+        return np.zeros(image.shape, dtype=bool)
     vertices = hull.points[hull.vertices]
 
     # If 2D, use fast Cython function to locate convex hull pixels


Test patch:
diff --git a/skimage/morphology/tests/test_convex_hull.py b/skimage/morphology/tests/test_convex_hull.py
--- a/skimage/morphology/tests/test_convex_hull.py
+++ b/skimage/morphology/tests/test_convex_hull.py
@@ -168,3 +168,17 @@ def test_consistent_2d_3d_hulls(images2d3d):
     chimage[8, 0] = True  # correct for single point exactly on hull edge
     chimage3d = convex_hull_image(image3d)
     assert_array_equal(chimage3d[1], chimage)
+
+
+def test_few_points():
+    image = np.array(
+        [[0, 0, 0, 0, 0, 0, 0, 0, 0],
+         [0, 0, 0, 0, 0, 0, 0, 1, 0],
+         [0, 0, 0, 0, 0, 0, 0, 0, 0],
+         [0, 0, 0, 0, 0, 0, 0, 0, 0],
+         [0, 1, 0, 0, 0, 0, 0, 0, 0],
+         [0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
+    image3d = np.stack([image, image, image])
+    with testing.assert_warns(UserWarning):
+        chimage3d = convex_hull_image(image3d)
+        assert_array_equal(chimage3d, np.zeros(image3d.shape, dtype=bool))