We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `skimage/morphology/convex_hull.py`
Content:
```
1 """Convex Hull."""
2 from itertools import product
3 import numpy as np
4 from scipy.spatial import ConvexHull
5 from ..measure.pnpoly import grid_points_in_poly
6 from ._convex_hull import possible_hull
7 from ..measure._label import label
8 from ..util import unique_rows
9 from .._shared.utils import warn
10 
11 __all__ = ['convex_hull_image', 'convex_hull_object']
12 
13 
14 def _offsets_diamond(ndim):
15     offsets = np.zeros((2 * ndim, ndim))
16     for vertex, (axis, offset) in enumerate(product(range(ndim), (-0.5, 0.5))):
17         offsets[vertex, axis] = offset
18     return offsets
19 
20 
21 def _check_coords_in_hull(gridcoords, hull_equations, tolerance):
22     r"""Checks all the coordinates for inclusiveness in the convex hull.
23 
24     Parameters
25     ----------
26     gridcoords : (M, N) ndarray
27         Coordinates of ``N`` points in ``M`` dimensions.
28     hull_equations : (M, N) ndarray
29         Hyperplane equations of the facets of the convex hull.
30     tolerance : float
31         Tolerance when determining whether a point is inside the hull. Due
32         to numerical floating point errors, a tolerance of 0 can result in
33         some points erroneously being classified as being outside the hull.
34 
35     Returns
36     -------
37     coords_in_hull : ndarray of bool
38         Binary 1D ndarray representing points in n-dimensional space
39         with value ``True`` set for points inside the convex hull.
40 
41     Notes
42     -----
43     Checking the inclusiveness of coordinates in a convex hull requires
44     intermediate calculations of dot products which are memory-intensive.
45     Thus, the convex hull equations are checked individually with all
46     coordinates to keep within the memory limit.
47 
48     References
49     ----------
50     .. [1] https://github.com/scikit-image/scikit-image/issues/5019
51 
52     """
53     ndim, n_coords = gridcoords.shape
54     n_hull_equations = hull_equations.shape[0]
55     coords_in_hull = np.ones(n_coords, dtype=bool)
56 
57     # Pre-allocate arrays to cache intermediate results for reducing overheads
58     dot_array = np.empty(n_coords, dtype=np.float64)
59     test_ineq_temp = np.empty(n_coords, dtype=np.float64)
60     coords_single_ineq = np.empty(n_coords, dtype=bool)
61 
62     # A point is in the hull if it satisfies all of the hull's inequalities
63     for idx in range(n_hull_equations):
64         # Tests a hyperplane equation on all coordinates of volume
65         np.dot(hull_equations[idx, :ndim], gridcoords, out=dot_array)
66         np.add(dot_array, hull_equations[idx, ndim:], out=test_ineq_temp)
67         np.less(test_ineq_temp, tolerance, out=coords_single_ineq)
68         coords_in_hull *= coords_single_ineq
69 
70     return coords_in_hull
71 
72 
73 def convex_hull_image(image, offset_coordinates=True, tolerance=1e-10):
74     """Compute the convex hull image of a binary image.
75 
76     The convex hull is the set of pixels included in the smallest convex
77     polygon that surround all white pixels in the input image.
78 
79     Parameters
80     ----------
81     image : array
82         Binary input image. This array is cast to bool before processing.
83     offset_coordinates : bool, optional
84         If ``True``, a pixel at coordinate, e.g., (4, 7) will be represented
85         by coordinates (3.5, 7), (4.5, 7), (4, 6.5), and (4, 7.5). This adds
86         some "extent" to a pixel when computing the hull.
87     tolerance : float, optional
88         Tolerance when determining whether a point is inside the hull. Due
89         to numerical floating point errors, a tolerance of 0 can result in
90         some points erroneously being classified as being outside the hull.
91 
92     Returns
93     -------
94     hull : (M, N) array of bool
95         Binary image with pixels in convex hull set to True.
96 
97     References
98     ----------
99     .. [1] https://blogs.mathworks.com/steve/2011/10/04/binary-image-convex-hull-algorithm-notes/
100 
101     """
102     ndim = image.ndim
103     if np.count_nonzero(image) == 0:
104         warn("Input image is entirely zero, no valid convex hull. "
105              "Returning empty image", UserWarning)
106         return np.zeros(image.shape, dtype=bool)
107     # In 2D, we do an optimisation by choosing only pixels that are
108     # the starting or ending pixel of a row or column.  This vastly
109     # limits the number of coordinates to examine for the virtual hull.
110     if ndim == 2:
111         coords = possible_hull(np.ascontiguousarray(image, dtype=np.uint8))
112     else:
113         coords = np.transpose(np.nonzero(image))
114         if offset_coordinates:
115             # when offsetting, we multiply number of vertices by 2 * ndim.
116             # therefore, we reduce the number of coordinates by using a
117             # convex hull on the original set, before offsetting.
118             hull0 = ConvexHull(coords)
119             coords = hull0.points[hull0.vertices]
120 
121     # Add a vertex for the middle of each pixel edge
122     if offset_coordinates:
123         offsets = _offsets_diamond(image.ndim)
124         coords = (coords[:, np.newaxis, :] + offsets).reshape(-1, ndim)
125 
126     # repeated coordinates can *sometimes* cause problems in
127     # scipy.spatial.ConvexHull, so we remove them.
128     coords = unique_rows(coords)
129 
130     # Find the convex hull
131     hull = ConvexHull(coords)
132     vertices = hull.points[hull.vertices]
133 
134     # If 2D, use fast Cython function to locate convex hull pixels
135     if ndim == 2:
136         mask = grid_points_in_poly(image.shape, vertices)
137     else:
138         gridcoords = np.reshape(np.mgrid[tuple(map(slice, image.shape))],
139                                 (ndim, -1))
140 
141         coords_in_hull = _check_coords_in_hull(gridcoords,
142                                                hull.equations, tolerance)
143         mask = np.reshape(coords_in_hull, image.shape)
144 
145     return mask
146 
147 
148 def convex_hull_object(image, *, connectivity=2):
149     r"""Compute the convex hull image of individual objects in a binary image.
150 
151     The convex hull is the set of pixels included in the smallest convex
152     polygon that surround all white pixels in the input image.
153 
154     Parameters
155     ----------
156     image : (M, N) ndarray
157         Binary input image.
158     connectivity : {1, 2}, int, optional
159         Determines the neighbors of each pixel. Adjacent elements
160         within a squared distance of ``connectivity`` from pixel center
161         are considered neighbors.::
162 
163             1-connectivity      2-connectivity
164                   [ ]           [ ]  [ ]  [ ]
165                    |               \  |  /
166              [ ]--[x]--[ ]      [ ]--[x]--[ ]
167                    |               /  |  \
168                   [ ]           [ ]  [ ]  [ ]
169 
170     Returns
171     -------
172     hull : ndarray of bool
173         Binary image with pixels inside convex hull set to ``True``.
174 
175     Notes
176     -----
177     This function uses ``skimage.morphology.label`` to define unique objects,
178     finds the convex hull of each using ``convex_hull_image``, and combines
179     these regions with logical OR. Be aware the convex hulls of unconnected
180     objects may overlap in the result. If this is suspected, consider using
181     convex_hull_image separately on each object or adjust ``connectivity``.
182     """
183     if image.ndim > 2:
184         raise ValueError("Input must be a 2D image")
185 
186     if connectivity not in (1, 2):
187         raise ValueError('`connectivity` must be either 1 or 2.')
188 
189     labeled_im = label(image, connectivity=connectivity, background=0)
190     convex_obj = np.zeros(image.shape, dtype=bool)
191     convex_img = np.zeros(image.shape, dtype=bool)
192 
193     for i in range(1, labeled_im.max() + 1):
194         convex_obj = convex_hull_image(labeled_im == i)
195         convex_img = np.logical_or(convex_img, convex_obj)
196 
197     return convex_img
198
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
             # 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 @@
     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

{"golden_diff": "diff --git a/skimage/morphology/convex_hull.py b/skimage/morphology/convex_hull.py\n--- a/skimage/morphology/convex_hull.py\n+++ b/skimage/morphology/convex_hull.py\n@@ -8,6 +8,13 @@\n from ..util import unique_rows\n from .._shared.utils import warn\n \n+try:\n+    # Should be public API of scipy spatial once #15003 is released\n+    # see https://github.com/scipy/scipy/pull/15003\n+    from scipy.spatial import QhullError\n+except ImportError:\n+    from scipy.spatial.qhull import QhullError\n+\n __all__ = ['convex_hull_image', 'convex_hull_object']\n \n \n@@ -115,7 +122,13 @@\n             # when offsetting, we multiply number of vertices by 2 * ndim.\n             # therefore, we reduce the number of coordinates by using a\n             # convex hull on the original set, before offsetting.\n-            hull0 = ConvexHull(coords)\n+            try:\n+                hull0 = ConvexHull(coords)\n+            except QhullError as err:\n+                warn(f\"Failed to get convex hull image. \"\n+                     f\"Returning empty image, see error message below:\\n\"\n+                     f\"{err}\")\n+                return np.zeros(image.shape, dtype=bool)\n             coords = hull0.points[hull0.vertices]\n \n     # Add a vertex for the middle of each pixel edge\n@@ -128,7 +141,13 @@\n     coords = unique_rows(coords)\n \n     # Find the convex hull\n-    hull = ConvexHull(coords)\n+    try:\n+        hull = ConvexHull(coords)\n+    except QhullError as err:\n+        warn(f\"Failed to get convex hull image. \"\n+             f\"Returning empty image, see error message below:\\n\"\n+             f\"{err}\")\n+        return np.zeros(image.shape, dtype=bool)\n     vertices = hull.points[hull.vertices]\n \n     # If 2D, use fast Cython function to locate convex hull pixels\n", "issue": "Convex Hull - dependent features can trip up regionprops calculation\n## Description\r\n\r\nFor some degenerate objects (e.g. intrinisically one less dimension, too few voxels), convex hull calculation throws exceptions.\r\nThis has been reported by Yi (@sunyi000 on Github) here \r\n https://forum.image.sc/t/scikit-image-regionprops-and-regionprops-table-questions/52021/2\r\nfor intrinsically 1d objects. In this scase scipy throws the following exception. @jni has also been part of that image.sc thread.\r\n\r\n```\r\nscipy.spatial.qhull.QhullError: QH6013 qhull input error: input is less than 3-dimensional since all points have the same x coordinate    0\r\n```\r\nOther objects that can cause such exceptions are those with too few points, see for example:\r\nhttps://github.com/scikit-image/scikit-image/issues/4865#issuecomment-711069382\r\n\r\n## Way to reproduce\r\nStill working on a minimal reproducible example\r\n\r\n## Suggested way to fix\r\n\r\nIMO,\r\nrather 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.\n", "before_files": [{"content": "\"\"\"Convex Hull.\"\"\"\nfrom itertools import product\nimport numpy as np\nfrom scipy.spatial import ConvexHull\nfrom ..measure.pnpoly import grid_points_in_poly\nfrom ._convex_hull import possible_hull\nfrom ..measure._label import label\nfrom ..util import unique_rows\nfrom .._shared.utils import warn\n\n__all__ = ['convex_hull_image', 'convex_hull_object']\n\n\ndef _offsets_diamond(ndim):\n    offsets = np.zeros((2 * ndim, ndim))\n    for vertex, (axis, offset) in enumerate(product(range(ndim), (-0.5, 0.5))):\n        offsets[vertex, axis] = offset\n    return offsets\n\n\ndef _check_coords_in_hull(gridcoords, hull_equations, tolerance):\n    r\"\"\"Checks all the coordinates for inclusiveness in the convex hull.\n\n    Parameters\n    ----------\n    gridcoords : (M, N) ndarray\n        Coordinates of ``N`` points in ``M`` dimensions.\n    hull_equations : (M, N) ndarray\n        Hyperplane equations of the facets of the convex hull.\n    tolerance : float\n        Tolerance when determining whether a point is inside the hull. Due\n        to numerical floating point errors, a tolerance of 0 can result in\n        some points erroneously being classified as being outside the hull.\n\n    Returns\n    -------\n    coords_in_hull : ndarray of bool\n        Binary 1D ndarray representing points in n-dimensional space\n        with value ``True`` set for points inside the convex hull.\n\n    Notes\n    -----\n    Checking the inclusiveness of coordinates in a convex hull requires\n    intermediate calculations of dot products which are memory-intensive.\n    Thus, the convex hull equations are checked individually with all\n    coordinates to keep within the memory limit.\n\n    References\n    ----------\n    .. [1] https://github.com/scikit-image/scikit-image/issues/5019\n\n    \"\"\"\n    ndim, n_coords = gridcoords.shape\n    n_hull_equations = hull_equations.shape[0]\n    coords_in_hull = np.ones(n_coords, dtype=bool)\n\n    # Pre-allocate arrays to cache intermediate results for reducing overheads\n    dot_array = np.empty(n_coords, dtype=np.float64)\n    test_ineq_temp = np.empty(n_coords, dtype=np.float64)\n    coords_single_ineq = np.empty(n_coords, dtype=bool)\n\n    # A point is in the hull if it satisfies all of the hull's inequalities\n    for idx in range(n_hull_equations):\n        # Tests a hyperplane equation on all coordinates of volume\n        np.dot(hull_equations[idx, :ndim], gridcoords, out=dot_array)\n        np.add(dot_array, hull_equations[idx, ndim:], out=test_ineq_temp)\n        np.less(test_ineq_temp, tolerance, out=coords_single_ineq)\n        coords_in_hull *= coords_single_ineq\n\n    return coords_in_hull\n\n\ndef convex_hull_image(image, offset_coordinates=True, tolerance=1e-10):\n    \"\"\"Compute the convex hull image of a binary image.\n\n    The convex hull is the set of pixels included in the smallest convex\n    polygon that surround all white pixels in the input image.\n\n    Parameters\n    ----------\n    image : array\n        Binary input image. This array is cast to bool before processing.\n    offset_coordinates : bool, optional\n        If ``True``, a pixel at coordinate, e.g., (4, 7) will be represented\n        by coordinates (3.5, 7), (4.5, 7), (4, 6.5), and (4, 7.5). This adds\n        some \"extent\" to a pixel when computing the hull.\n    tolerance : float, optional\n        Tolerance when determining whether a point is inside the hull. Due\n        to numerical floating point errors, a tolerance of 0 can result in\n        some points erroneously being classified as being outside the hull.\n\n    Returns\n    -------\n    hull : (M, N) array of bool\n        Binary image with pixels in convex hull set to True.\n\n    References\n    ----------\n    .. [1] https://blogs.mathworks.com/steve/2011/10/04/binary-image-convex-hull-algorithm-notes/\n\n    \"\"\"\n    ndim = image.ndim\n    if np.count_nonzero(image) == 0:\n        warn(\"Input image is entirely zero, no valid convex hull. \"\n             \"Returning empty image\", UserWarning)\n        return np.zeros(image.shape, dtype=bool)\n    # In 2D, we do an optimisation by choosing only pixels that are\n    # the starting or ending pixel of a row or column.  This vastly\n    # limits the number of coordinates to examine for the virtual hull.\n    if ndim == 2:\n        coords = possible_hull(np.ascontiguousarray(image, dtype=np.uint8))\n    else:\n        coords = np.transpose(np.nonzero(image))\n        if offset_coordinates:\n            # when offsetting, we multiply number of vertices by 2 * ndim.\n            # therefore, we reduce the number of coordinates by using a\n            # convex hull on the original set, before offsetting.\n            hull0 = ConvexHull(coords)\n            coords = hull0.points[hull0.vertices]\n\n    # Add a vertex for the middle of each pixel edge\n    if offset_coordinates:\n        offsets = _offsets_diamond(image.ndim)\n        coords = (coords[:, np.newaxis, :] + offsets).reshape(-1, ndim)\n\n    # repeated coordinates can *sometimes* cause problems in\n    # scipy.spatial.ConvexHull, so we remove them.\n    coords = unique_rows(coords)\n\n    # Find the convex hull\n    hull = ConvexHull(coords)\n    vertices = hull.points[hull.vertices]\n\n    # If 2D, use fast Cython function to locate convex hull pixels\n    if ndim == 2:\n        mask = grid_points_in_poly(image.shape, vertices)\n    else:\n        gridcoords = np.reshape(np.mgrid[tuple(map(slice, image.shape))],\n                                (ndim, -1))\n\n        coords_in_hull = _check_coords_in_hull(gridcoords,\n                                               hull.equations, tolerance)\n        mask = np.reshape(coords_in_hull, image.shape)\n\n    return mask\n\n\ndef convex_hull_object(image, *, connectivity=2):\n    r\"\"\"Compute the convex hull image of individual objects in a binary image.\n\n    The convex hull is the set of pixels included in the smallest convex\n    polygon that surround all white pixels in the input image.\n\n    Parameters\n    ----------\n    image : (M, N) ndarray\n        Binary input image.\n    connectivity : {1, 2}, int, optional\n        Determines the neighbors of each pixel. Adjacent elements\n        within a squared distance of ``connectivity`` from pixel center\n        are considered neighbors.::\n\n            1-connectivity      2-connectivity\n                  [ ]           [ ]  [ ]  [ ]\n                   |               \\  |  /\n             [ ]--[x]--[ ]      [ ]--[x]--[ ]\n                   |               /  |  \\\n                  [ ]           [ ]  [ ]  [ ]\n\n    Returns\n    -------\n    hull : ndarray of bool\n        Binary image with pixels inside convex hull set to ``True``.\n\n    Notes\n    -----\n    This function uses ``skimage.morphology.label`` to define unique objects,\n    finds the convex hull of each using ``convex_hull_image``, and combines\n    these regions with logical OR. Be aware the convex hulls of unconnected\n    objects may overlap in the result. If this is suspected, consider using\n    convex_hull_image separately on each object or adjust ``connectivity``.\n    \"\"\"\n    if image.ndim > 2:\n        raise ValueError(\"Input must be a 2D image\")\n\n    if connectivity not in (1, 2):\n        raise ValueError('`connectivity` must be either 1 or 2.')\n\n    labeled_im = label(image, connectivity=connectivity, background=0)\n    convex_obj = np.zeros(image.shape, dtype=bool)\n    convex_img = np.zeros(image.shape, dtype=bool)\n\n    for i in range(1, labeled_im.max() + 1):\n        convex_obj = convex_hull_image(labeled_im == i)\n        convex_img = np.logical_or(convex_img, convex_obj)\n\n    return convex_img\n", "path": "skimage/morphology/convex_hull.py"}], "after_files": [{"content": "\"\"\"Convex Hull.\"\"\"\nfrom itertools import product\nimport numpy as np\nfrom scipy.spatial import ConvexHull\nfrom ..measure.pnpoly import grid_points_in_poly\nfrom ._convex_hull import possible_hull\nfrom ..measure._label import label\nfrom ..util import unique_rows\nfrom .._shared.utils import warn\n\ntry:\n    # Should be public API of scipy spatial once #15003 is released\n    # see https://github.com/scipy/scipy/pull/15003\n    from scipy.spatial import QhullError\nexcept ImportError:\n    from scipy.spatial.qhull import QhullError\n\n__all__ = ['convex_hull_image', 'convex_hull_object']\n\n\ndef _offsets_diamond(ndim):\n    offsets = np.zeros((2 * ndim, ndim))\n    for vertex, (axis, offset) in enumerate(product(range(ndim), (-0.5, 0.5))):\n        offsets[vertex, axis] = offset\n    return offsets\n\n\ndef _check_coords_in_hull(gridcoords, hull_equations, tolerance):\n    r\"\"\"Checks all the coordinates for inclusiveness in the convex hull.\n\n    Parameters\n    ----------\n    gridcoords : (M, N) ndarray\n        Coordinates of ``N`` points in ``M`` dimensions.\n    hull_equations : (M, N) ndarray\n        Hyperplane equations of the facets of the convex hull.\n    tolerance : float\n        Tolerance when determining whether a point is inside the hull. Due\n        to numerical floating point errors, a tolerance of 0 can result in\n        some points erroneously being classified as being outside the hull.\n\n    Returns\n    -------\n    coords_in_hull : ndarray of bool\n        Binary 1D ndarray representing points in n-dimensional space\n        with value ``True`` set for points inside the convex hull.\n\n    Notes\n    -----\n    Checking the inclusiveness of coordinates in a convex hull requires\n    intermediate calculations of dot products which are memory-intensive.\n    Thus, the convex hull equations are checked individually with all\n    coordinates to keep within the memory limit.\n\n    References\n    ----------\n    .. [1] https://github.com/scikit-image/scikit-image/issues/5019\n\n    \"\"\"\n    ndim, n_coords = gridcoords.shape\n    n_hull_equations = hull_equations.shape[0]\n    coords_in_hull = np.ones(n_coords, dtype=bool)\n\n    # Pre-allocate arrays to cache intermediate results for reducing overheads\n    dot_array = np.empty(n_coords, dtype=np.float64)\n    test_ineq_temp = np.empty(n_coords, dtype=np.float64)\n    coords_single_ineq = np.empty(n_coords, dtype=bool)\n\n    # A point is in the hull if it satisfies all of the hull's inequalities\n    for idx in range(n_hull_equations):\n        # Tests a hyperplane equation on all coordinates of volume\n        np.dot(hull_equations[idx, :ndim], gridcoords, out=dot_array)\n        np.add(dot_array, hull_equations[idx, ndim:], out=test_ineq_temp)\n        np.less(test_ineq_temp, tolerance, out=coords_single_ineq)\n        coords_in_hull *= coords_single_ineq\n\n    return coords_in_hull\n\n\ndef convex_hull_image(image, offset_coordinates=True, tolerance=1e-10):\n    \"\"\"Compute the convex hull image of a binary image.\n\n    The convex hull is the set of pixels included in the smallest convex\n    polygon that surround all white pixels in the input image.\n\n    Parameters\n    ----------\n    image : array\n        Binary input image. This array is cast to bool before processing.\n    offset_coordinates : bool, optional\n        If ``True``, a pixel at coordinate, e.g., (4, 7) will be represented\n        by coordinates (3.5, 7), (4.5, 7), (4, 6.5), and (4, 7.5). This adds\n        some \"extent\" to a pixel when computing the hull.\n    tolerance : float, optional\n        Tolerance when determining whether a point is inside the hull. Due\n        to numerical floating point errors, a tolerance of 0 can result in\n        some points erroneously being classified as being outside the hull.\n\n    Returns\n    -------\n    hull : (M, N) array of bool\n        Binary image with pixels in convex hull set to True.\n\n    References\n    ----------\n    .. [1] https://blogs.mathworks.com/steve/2011/10/04/binary-image-convex-hull-algorithm-notes/\n\n    \"\"\"\n    ndim = image.ndim\n    if np.count_nonzero(image) == 0:\n        warn(\"Input image is entirely zero, no valid convex hull. \"\n             \"Returning empty image\", UserWarning)\n        return np.zeros(image.shape, dtype=bool)\n    # In 2D, we do an optimisation by choosing only pixels that are\n    # the starting or ending pixel of a row or column.  This vastly\n    # limits the number of coordinates to examine for the virtual hull.\n    if ndim == 2:\n        coords = possible_hull(np.ascontiguousarray(image, dtype=np.uint8))\n    else:\n        coords = np.transpose(np.nonzero(image))\n        if offset_coordinates:\n            # when offsetting, we multiply number of vertices by 2 * ndim.\n            # therefore, we reduce the number of coordinates by using a\n            # convex hull on the original set, before offsetting.\n            try:\n                hull0 = ConvexHull(coords)\n            except QhullError as err:\n                warn(f\"Failed to get convex hull image. \"\n                     f\"Returning empty image, see error message below:\\n\"\n                     f\"{err}\")\n                return np.zeros(image.shape, dtype=bool)\n            coords = hull0.points[hull0.vertices]\n\n    # Add a vertex for the middle of each pixel edge\n    if offset_coordinates:\n        offsets = _offsets_diamond(image.ndim)\n        coords = (coords[:, np.newaxis, :] + offsets).reshape(-1, ndim)\n\n    # repeated coordinates can *sometimes* cause problems in\n    # scipy.spatial.ConvexHull, so we remove them.\n    coords = unique_rows(coords)\n\n    # Find the convex hull\n    try:\n        hull = ConvexHull(coords)\n    except QhullError as err:\n        warn(f\"Failed to get convex hull image. \"\n             f\"Returning empty image, see error message below:\\n\"\n             f\"{err}\")\n        return np.zeros(image.shape, dtype=bool)\n    vertices = hull.points[hull.vertices]\n\n    # If 2D, use fast Cython function to locate convex hull pixels\n    if ndim == 2:\n        mask = grid_points_in_poly(image.shape, vertices)\n    else:\n        gridcoords = np.reshape(np.mgrid[tuple(map(slice, image.shape))],\n                                (ndim, -1))\n\n        coords_in_hull = _check_coords_in_hull(gridcoords,\n                                               hull.equations, tolerance)\n        mask = np.reshape(coords_in_hull, image.shape)\n\n    return mask\n\n\ndef convex_hull_object(image, *, connectivity=2):\n    r\"\"\"Compute the convex hull image of individual objects in a binary image.\n\n    The convex hull is the set of pixels included in the smallest convex\n    polygon that surround all white pixels in the input image.\n\n    Parameters\n    ----------\n    image : (M, N) ndarray\n        Binary input image.\n    connectivity : {1, 2}, int, optional\n        Determines the neighbors of each pixel. Adjacent elements\n        within a squared distance of ``connectivity`` from pixel center\n        are considered neighbors.::\n\n            1-connectivity      2-connectivity\n                  [ ]           [ ]  [ ]  [ ]\n                   |               \\  |  /\n             [ ]--[x]--[ ]      [ ]--[x]--[ ]\n                   |               /  |  \\\n                  [ ]           [ ]  [ ]  [ ]\n\n    Returns\n    -------\n    hull : ndarray of bool\n        Binary image with pixels inside convex hull set to ``True``.\n\n    Notes\n    -----\n    This function uses ``skimage.morphology.label`` to define unique objects,\n    finds the convex hull of each using ``convex_hull_image``, and combines\n    these regions with logical OR. Be aware the convex hulls of unconnected\n    objects may overlap in the result. If this is suspected, consider using\n    convex_hull_image separately on each object or adjust ``connectivity``.\n    \"\"\"\n    if image.ndim > 2:\n        raise ValueError(\"Input must be a 2D image\")\n\n    if connectivity not in (1, 2):\n        raise ValueError('`connectivity` must be either 1 or 2.')\n\n    labeled_im = label(image, connectivity=connectivity, background=0)\n    convex_obj = np.zeros(image.shape, dtype=bool)\n    convex_img = np.zeros(image.shape, dtype=bool)\n\n    for i in range(1, labeled_im.max() + 1):\n        convex_obj = convex_hull_image(labeled_im == i)\n        convex_img = np.logical_or(convex_img, convex_obj)\n\n    return convex_img\n", "path": "skimage/morphology/convex_hull.py"}]}