Repository: scikit-image/scikit-image

Issue:
some edge points not included in points_in_poly result
## Description
I've been trying to rasterize points in a polygon (like `grid_points_in_poly`) but have been getting unintuitive/incorrect results due to certain edge points being deemed not inside the polygon by your algorithm.

Specifically, the problem seems to arise because the points are counted inclusively only on the lesser side of the equation (`min <= point < max`).

This is most obvious when checking if a polygon's vertices are counted as inside the polygon:
```python
In [1]: from skimage.measure import points_in_poly

In [2]: verts = [(0, 0), (1, 0), (0, 1)]

In [3]: points_in_poly(verts, verts)
Out[3]: array([ True, False, False])
```

I wanted to check in if this is intentional or a bug since this is not the output I would expect.


Discussion:
:wave:

Generally, I like to be consistent with Python's half-open indexing, but your example makes a compelling case that it is the wrong thing to do here. If others in @scikit-image/core agree, I suggest we fix this as a bug (no deprecation) and add your example as a test case.

I think other tests depend on this, so it might be more painful than it sounds.
You generally shouldn't count on the behaviour of these edge cases.

Feel free to experiment with the likes of 

```python
$ python                                                                                                
Python 3.7.3 | packaged by conda-forge | (default, Mar 27 2019, 23:01:00) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> (1. * 3 + 1 + 1) == 5.
True
>>> (1. * 3 + 1. + 1.) == 5.
True
>>> (1. * 3 + 1.001 + 0.999) == 5.
False
```

Generally, don't count on equality for floating point numbers.

IMO, this isn't a bug, and you shouldn't count on the "edge" to be "inside" or "outside" of things.
@hmaarrfk I'm well aware of this (see https://github.com/scikit-image/scikit-image/pull/2847#issuecomment-339850986), but in the case of *same data*, equality makes sense, and given that it's easy, we should probably do the correct thing in that case. I think the implication of your statement is that if we fix @kne42's issue by replacing `<` with `<=`, then no one should rightly complain. ;)
That comment exactly.

The issue is that "guaranteeing that we check for the corners" is an implementation detail. What if newer implementations want to cache some intermediate representation avoid the comparison with the original data.

Mind you, the original data is already discarded pretty quickly,
https://github.com/scikit-image/scikit-image/blob/master/skimage/measure/_pnpoly.pyx#L84
```
    x = points[:, 0].astype(np.double)
    y = points[:, 1].astype(np.double)

    vx = verts[:, 0].astype(np.double)
    vy = verts[:, 1].astype(np.double)
```

I think this should be left as is, and avoid any special casing.

Finally, you already have some kind of open right hand set going on
https://github.com/scikit-image/scikit-image/blob/master/skimage/_shared/geometry.pyx#L26

Not much more we can do about that.
I'm confused by the talk of "half-open interval" here; with a polygon,
there's no opening and closing side.

As far as I recall, this algorithm is from
http://paulbourke.net/geometry/polygonmesh/

NIST uses the same in their software:
https://www.itl.nist.gov/div898/software/dataplot/refman2/auxillar/poinpoly.htm

I was having problems getting my scanline algorithm to give consistent
results with point-in-polygon:
https://github.com/scikit-image/scikit-image/pull/931 Perhaps this is
related?

> I'm confused by the talk of "half-open interval" here; with a polygon,
> there's no opening and closing side.

I agree, sorry I brought it up! :joy: It's more that `lo <= val < hi` *reminds me* of half-open intervals and that is appealing in a hand-wavy and not-at-all-what-one-should-base-decisions-on way.

> As far as I recall, the algorithm is from

Yep, it's right there pretty much verbatim from our code. This quote from that article argues exactly against @hmaarrfk's argument though:

> Quote: "For most of the algorithms above there is a pathological case if the point being queried lies exactly on a vertex. The easiest way to cope with this is to test that as a separate process and make your own decision as to whether you want to consider them inside or outside."

The NIST page also argues for a small tolerance when it comes to checking the vertices:

> In deterining if a point is equal to a vertex point, Dataplot checks that the x-coordinate and the y-coordinate are within epsilon of each vertex where epsilon is set equal to 0.0000001.

I guess if you want to follow NIST's definiton, you would have to return one of 4 different values

```
Each value of the tag variable will contain either a 1, 2, 3, or 4 where

    1 = interior point
    2 = exterior point
    3 = edge point
    4 = vertex point
```
I would consider this issue as a bug because of the inconsistency of the output result in the example provided by @kne42 (some vertices are inside, other not).
Considering that the vertices or the edge points are inside or outside a polygon is a matter of choice, It only has to be be clearly stated and documented.
A more general approach, as pointed by @hmaarrfk, would be to follow the NIST's definition that detects the four possible cases and lets the user decide whether the vertices are inside or outside the polygon.
I like that 4-case mapping a lot!  [In the face of ambiguity, refuse the temptation to guess.](https://www.python.org/dev/peps/pep-0020/).
NIST documentation points to _O'Rourke (1998), "Computational Geometry in C", Second Edition, Cambridge Unversity Press, Chapter 7_.
The associated C code is available [here](http://cs.smith.edu/~jorourke/books/ftp.html).

Using the convention

> 0: outside
> 1: inside
> 2: vertex
> 3: edge

still allows to test `if point_in_poly(P, poly): ...`
I've gone ahead and made a Cython implementation of the above algorithm using the convention proposed by @rfezzani.

<details><summary>code</summary>

```cython
from libc.math cimport fabs


cdef enum:
    OUTSIDE = 0
    INSIDE  = 1
    VERTEX  = 2
    EDGE    = 3


cdef unsigned char point_in_polygon(Py_ssize_t nr_verts, double *xp,
                                    double *yp, double x, double y,
                                    double epsilon=0.0000001) nogil:
    """Classify where a point lies with respect to a polygon
    using the ray-crossing algorithm [1]_.

    Parameters
    ----------
    nr_verts : int
        Number of vertices of polygon.
    xp, yp : double array
        Coordinates of polygon with length nr_verts.
    x, y : double
        Coordinates of point.
    epsilon : double, optional
        Error limit with which to check if point is a vertex.

    Returns
    -------
    classification : unsigned char
        Classification of the point: 0 if outside, 1 if inside, 2 if a vertex,
        and 3 if an edge.

    References
    ----------
    .. [1] O'Rourke (1998), "Computational Geometry in C",
           Second Edition, Cambridge Unversity Press, Chapter 7
    """
    cdef:
        unsigned int r_cross = 0     # number of right edge/ray crossings
        unsigned int l_cross = 0     # number of left edge/ray crossings

        Py_ssize_t i                 # point index
        Py_ssize_t j = nr_verts - 1  # previous point index

        double x0, y0                # vertex when point is origin
        double x1, y1                # previous vertex when point is origin
    
    for i in range(nr_verts):
        x0 = xp[i] - x
        y0 = yp[i] - y

        if fabs(x0) < epsilon and fabs(y0) < epsilon:
            # it's a vertex
            return VERTEX

        x1 = xp[j] - x
        y1 = yp[j] - y
        
        if (
            # straddles the x-component of the ray
            y0 > 0 != y1 > 0
            # crosses the ray if strictly positive intersection
            and (x0 * y1 - x1 * y0) / (y1 - y0) > 0
        ):
            r_cross += 1
            
        if (
            # straddles the x-component of the ray when reversed
            y0 < 0 != y1 < 0
            # crosses the ray if strictly negative intersection
            and (x0 * y1 - x1 * y0) / (y1 - y0) < 0
        ):
            l_cross += 1
        
        j = i

    if r_cross % 2 != l_cross % 2:
        # on edge if left and right crossings not of same parity
        return EDGE
    elif r_cross % 2:
        # inside if odd number of crossings
        return INSIDE
    else:
        # outside if even number of crossings
        return OUTSIDE
```
</details>

Determining the API, however, can be tricky when considering the following:
1. the output of `points_in_poly` and `grid_points_in_poly` must be `bool` because they need to function properly as masks
2. the "half-open interval" nature of the current implementation is actually useful for polygons that share the same boundary since their masks will then never overlap

So I have a few questions for y'all (where "test" is the current implementation and "classify" is the above):
- is (2) desirable and important to keep?
- if so, do we want two separate implementations for "test" and "classify" or retroactively clip the upper bound edges?
- do we want to make two different API endpoints (functions) for (1) or use an optional `as_mask` (or similar) argument?
- if using an optional argument, what should be the default?
- should internal functions use the "test" or the "classify" version?
On Tue, 11 Jun 2019 13:45:40 -0700, Kira Evans wrote:
> 2. the "half-open interval" nature of the current implementation is
> actually useful for polygons that share the same boundary since their
> masks will then never overlap

Can this currently be guaranteed?  I guess it should work pretty
reliably.

I wonder how much this would impact real use cases.  You can imagine
filling each polygon with a color, but once you are done it really
doesn't matter whether they overlapped—you simply overlap the result.
If you are doing something like counting, it would be more problematic.
In that case, you may have to do something like intersect all the
polygon masks which would be quite painful.

I wonder if we cannot come up with a multiple labeling that allows us to
map the result to what we had before?  It seems like it *should* be
possible.

Sorry @kne42 for this soo late answer

> - is (2) desirable and important to keep?

I think that it is a source of confusion and that it should be deprecated.

> - if so, do we want two separate implementations for "test" and "classify" or retroactively clip the upper bound edges?
> - do we want to make two different API endpoints (functions) for (1) or use an optional as_mask (or similar) argument?
> - if using an optional argument, what should be the default?
> - should internal functions use the "test" or the "classify" version?

I would personally vote for a unique implementation with the use of an extra argument `return_labels` set by default to `False` and use something like
```python
if return_labels:
    return labels

return labels > 0
```
The internal functions must then use the "classify" version.

> I would personally vote for a unique implementation with the use of an extra argument `return_labels` set by default to `False` and use something like
> 
> ```python
> if return_labels:
>     return labels
> 
> return labels > 0
> ```

I'm kindly against this. Having several `return` within a function is not good for us—e.g. for tests, it's not nice to return different datatypes in the same function. We can have unexpected behavior—, and I'm personally tracking the functions where we use it. I aim for a cleaner code, and I'd like also to remove some arguments as `return_something`. Our API will thank us in the future :)
Good point, I :-1: myself :slightly_smiling_face: .
If you want to motivate why a certain API decision is unwise, you have provide clear reasons.  The meaning of "not good for us" and "not nice" are a subjective aesthetics call.  I've heard this argument by Robert Kern on the NumPy mailing list before, so I believe that there is a rough consensus that using flags to determine output types is not ideal.  You can see why: you don't want a function to behave substantially differently based on an argument.  But, I have not seen suggestions for alternatives, other than to make a whole new function.

Would, e.g., a combo of `segment` and `segment_with_labels` really be that much better than a flag?   Maybe.

W.r.t. [return statements](https://stackoverflow.com/questions/36707/should-a-function-have-only-one-return-statement), in *Code Complete* the author writes:

> 17.1 return
>
> Minimize the number of returns in each routine. It's harder to understand a routine if, reading it at the bottom, you're unaware of the possibility that it returned somewhere above.
>
> Use a return when it enhances readability. In certain routines, once you know the answer, you want to return it to the calling routine immediately. If the routine is defined in such a way that it doesn't require any cleanup, not returning immediately means that you have to write more code.

[Guard clauses](https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html) are a good example of where they can significantly simplify code logic.

To get back to the original issue, there seem to be several cases:

1. Two results are computed inside the function; questions is whether to return one or both.  Solution: always return both.
2. Result is computed; as a side-effect, information is available that makes a secondary result cheap to compute.  Question: should the second be computed, even if it takes up a lot of memory?
3. Result is computed; secondary result is computed from the result.  Solution: return only the result, and provide function to derive secondary result.

Case (2) is the one we need to think about carefully.
> If you want to motivate why a certain API decision is unwise, you have provide clear reasons. The meaning of "not good for us" and "not nice" are a subjective aesthetics call.

@stefanv thank you for your points, and sorry if I was not clear enough. You can attribute the missing reasons here to my lack of substance.
W.r.t. the original issue, if we think on API consistency, @rfezzani have a point: `return_something` (or @kne42 's suggestion `as_mask`) is something we've done before. However, if the result in 2. is easy to compute, couldn't we implement a variation on case 3.—showing in the docs how to obtain the secondary result?
The problem is with the case where you can *only* compute the secondary result effectively because of intermediate results that are available inside the function.
> The problem is with the case where you can only compute the secondary result effectively because of intermediate results that are available inside the function.

Yes, agreed. Here is a concrete example of this from `structural_similarity` (which varies what is returned based on both the `full` and `gradient` boolean arguments!)
https://github.com/scikit-image/scikit-image/blob/011058708988589b523ea524a270acc2737de0e5/skimage/metrics/_structural_similarity.py#L216-L232

If we decide to follow the rules stated by @stefanv, `_structural_similarity` API should be modified according to **1.**, right?
Do we need to follow these rules over all `skimage` API? If so, it is may be necessary to add them to the contribution guide.
Btw, I am :+1: with @alexdesiqueira proposition:
> implement a variation on case 3.—showing in the docs how to obtain the secondary result
Would you please @kne42 open a PR to include your beautiful Cython implementation? We can then Fix this bug and decide there which is the best API to adopt?
Thank you very much ;)

Resolving patch:
diff --git a/skimage/draw/_random_shapes.py b/skimage/draw/_random_shapes.py
--- a/skimage/draw/_random_shapes.py
+++ b/skimage/draw/_random_shapes.py
@@ -2,7 +2,7 @@
 import numpy as np
 
 from . import (polygon as draw_polygon, disk as draw_disk,
-    ellipse as draw_ellipse)
+               ellipse as draw_ellipse)
 from .._shared.utils import warn
 
 
@@ -16,7 +16,8 @@ def _generate_rectangle_mask(point, image, shape, random):
     point : tuple
         The row and column of the top left corner of the rectangle.
     image : tuple
-        The height, width and depth of the image into which the shape is placed.
+        The height, width and depth of the image into which the shape
+        is placed.
     shape : tuple
         The minimum and maximum size of the shape to fit.
     random : np.random.RandomState
@@ -36,16 +37,14 @@ def _generate_rectangle_mask(point, image, shape, random):
         bounding box coordinates of the shape.
     indices : 2-D array
         A mask of indices that the shape fills.
+
     """
-    available_width = min(image[1] - point[1], shape[1])
-    if available_width < shape[0]:
-        raise ArithmeticError('cannot fit shape to image')
-    available_height = min(image[0] - point[0], shape[1])
-    if available_height < shape[0]:
-        raise ArithmeticError('cannot fit shape to image')
+    available_width = min(image[1] - point[1], shape[1]) - shape[0]
+    available_height = min(image[0] - point[0], shape[1]) - shape[0]
+
     # Pick random widths and heights.
-    r = random.randint(shape[0], available_height + 1)
-    c = random.randint(shape[0], available_width + 1)
+    r = shape[0] + random.randint(max(1, available_height)) - 1
+    c = shape[0] + random.randint(max(1, available_width)) - 1
     rectangle = draw_polygon([
         point[0],
         point[0] + r,
@@ -57,7 +56,8 @@ def _generate_rectangle_mask(point, image, shape, random):
         point[1] + c,
         point[1] + c,
     ])
-    label = ('rectangle', ((point[0], point[0] + r), (point[1], point[1] + c)))
+    label = ('rectangle', ((point[0], point[0] + r + 1),
+                           (point[1], point[1] + c + 1)))
 
     return rectangle, label
 
@@ -95,16 +95,17 @@ def _generate_circle_mask(point, image, shape, random):
     """
     if shape[0] == 1 or shape[1] == 1:
         raise ValueError('size must be > 1 for circles')
-    min_radius = shape[0] / 2.0
-    max_radius = shape[1] / 2.0
+    min_radius = shape[0] // 2.0
+    max_radius = shape[1] // 2.0
     left = point[1]
     right = image[1] - point[1]
     top = point[0]
     bottom = image[0] - point[0]
-    available_radius = min(left, right, top, bottom, max_radius)
-    if available_radius < min_radius:
+    available_radius = min(left, right, top,
+                           bottom, max_radius) - min_radius
+    if available_radius < 0:
         raise ArithmeticError('cannot fit shape to image')
-    radius = random.randint(min_radius, available_radius + 1)
+    radius = int(min_radius + random.randint(max(1, available_radius)))
     # TODO: think about how to deprecate this
     # while draw_circle was deprecated in favor of draw_disk
     # switching to a label of 'disk' here
@@ -127,9 +128,10 @@ def _generate_triangle_mask(point, image, shape, random):
     Parameters
     ----------
     point : tuple
-        The row and column of the top left corner of a down-pointing triangle.
+        The row and column of the top left corner of a up-pointing triangle.
     image : tuple
-        The height, width and depth of the image into which the shape is placed.
+        The height, width and depth of the image into which the shape
+        is placed.
     shape : tuple
         The minimum and maximum size and color of the shape to fit.
     random : np.random.RandomState
@@ -149,13 +151,13 @@ def _generate_triangle_mask(point, image, shape, random):
         bounding box coordinates of the shape.
     indices : 2-D array
         A mask of indices that the shape fills.
+
     """
     if shape[0] == 1 or shape[1] == 1:
         raise ValueError('dimension must be > 1 for triangles')
-    available_side = min(image[1] - point[1], point[0] + 1, shape[1])
-    if available_side < shape[0]:
-        raise ArithmeticError('cannot fit shape to image')
-    side = random.randint(shape[0], available_side + 1)
+    available_side = min(image[1] - point[1], point[0],
+                         shape[1]) - shape[0]
+    side = shape[0] + random.randint(max(1, available_side)) - 1
     triangle_height = int(np.ceil(np.sqrt(3 / 4.0) * side))
     triangle = draw_polygon([
         point[0],
@@ -166,8 +168,8 @@ def _generate_triangle_mask(point, image, shape, random):
         point[1] + side // 2,
         point[1] + side,
     ])
-    label = ('triangle', ((point[0] - triangle_height, point[0]),
-                          (point[1], point[1] + side)))
+    label = ('triangle', ((point[0] - triangle_height, point[0] + 1),
+                          (point[1], point[1] + side + 1)))
 
     return triangle, label
 
@@ -241,7 +243,6 @@ def _generate_ellipse_mask(point, image, shape, random):
     return ellipse, label
 
 
-
 # Allows lookup by key as well as random selection.
 SHAPE_GENERATORS = dict(
     rectangle=_generate_rectangle_mask,
@@ -335,12 +336,13 @@ def random_shapes(image_shape,
     shape : {rectangle, circle, triangle, ellipse, None} str, optional
         The name of the shape to generate or `None` to pick random ones.
     intensity_range : {tuple of tuples of uint8, tuple of uint8}, optional
-        The range of values to sample pixel values from. For grayscale images
-        the format is (min, max). For multichannel - ((min, max),) if the
-        ranges are equal across the channels, and ((min_0, max_0), ... (min_N, max_N))
-        if they differ. As the function supports generation of uint8 arrays only,
-        the maximum range is (0, 255). If None, set to (0, 254) for each
-        channel reserving color of intensity = 255 for background.
+        The range of values to sample pixel values from. For grayscale
+        images the format is (min, max). For multichannel - ((min, max),)
+        if the ranges are equal across the channels, and
+        ((min_0, max_0), ... (min_N, max_N)) if they differ. As the
+        function supports generation of uint8 arrays only, the maximum
+        range is (0, 255). If None, set to (0, 254) for each channel
+        reserving color of intensity = 255 for background.
     allow_overlap : bool, optional
         If `True`, allow shapes to overlap.
     num_trials : int, optional
@@ -402,22 +404,23 @@ def random_shapes(image_shape,
     num_shapes = random.randint(min_shapes, max_shapes + 1)
     colors = _generate_random_colors(num_shapes, num_channels,
                                      intensity_range, random)
+    shape = (min_size, max_size)
     for shape_idx in range(num_shapes):
         if user_shape is None:
             shape_generator = random.choice(SHAPE_CHOICES)
         else:
             shape_generator = SHAPE_GENERATORS[user_shape]
-        shape = (min_size, max_size)
         for _ in range(num_trials):
             # Pick start coordinates.
-            column = random.randint(image_shape[1])
-            row = random.randint(image_shape[0])
+            column = random.randint(max(1, image_shape[1] - min_size))
+            row = random.randint(max(1, image_shape[0] - min_size))
             point = (row, column)
             try:
                 indices, label = shape_generator(point, image_shape, shape,
                                                  random)
             except ArithmeticError:
                 # Couldn't fit the shape, skip it.
+                indices = []
                 continue
             # Check if there is an overlap where the mask is nonzero.
             if allow_overlap or not filled[indices].any():
diff --git a/skimage/draw/draw.py b/skimage/draw/draw.py
--- a/skimage/draw/draw.py
+++ b/skimage/draw/draw.py
@@ -485,14 +485,14 @@ def polygon(r, c, shape=None):
     >>> img[rr, cc] = 1
     >>> img
     array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-           [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
+           [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
+           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
            [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
+           [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
            [0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
-           [0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
+           [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
            [0, 0, 0, 0, 1, 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, 0, 0, 0, 0, 0]], dtype=uint8)
 
     """


Test patch:
diff --git a/skimage/draw/tests/test_draw.py b/skimage/draw/tests/test_draw.py
--- a/skimage/draw/tests/test_draw.py
+++ b/skimage/draw/tests/test_draw.py
@@ -145,12 +145,13 @@ def test_line_equal_aliasing_horizontally_vertically():
 
 def test_polygon_rectangle():
     img = np.zeros((10, 10), 'uint8')
+    poly = np.array(((1, 1), (4, 1), (4, 4), (1, 4), (1, 1)))
 
-    rr, cc = polygon((1, 4, 4, 1, 1), (1, 1, 4, 4, 1))
+    rr, cc = polygon(poly[:, 0], poly[:, 1])
     img[rr, cc] = 1
 
-    img_ = np.zeros((10, 10))
-    img_[1:4, 1:4] = 1
+    img_ = np.zeros((10, 10), 'uint8')
+    img_[1:5, 1:5] = 1
 
     assert_array_equal(img, img_)
 
@@ -163,16 +164,16 @@ def test_polygon_rectangle_angular():
     img[rr, cc] = 1
 
     img_ = np.array(
-        [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-         [0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
-         [0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
-         [1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
-         [0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
-         [0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
-         [0, 0, 0, 1, 1, 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, 1, 1, 1, 0, 0, 0, 0, 0],
+         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
+         [1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
+         [0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
+         [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
+         [0, 0, 0, 1, 1, 1, 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, 0, 0, 0, 0, 0]]
+         [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 'uint8'
     )
 
     assert_array_equal(img, img_)
@@ -187,15 +188,15 @@ def test_polygon_parallelogram():
 
     img_ = np.array(
         [[0, 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, 1, 1, 1, 0, 0, 0, 0, 0, 0],
-         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
-         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
-         [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
-         [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
-         [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+         [0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
+         [0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
+         [0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
+         [0, 0, 0, 0, 1, 1, 1, 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, 0, 0, 0]]
+         [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 'uint8'
     )
 
     assert_array_equal(img, img_)
@@ -218,7 +219,7 @@ def test_polygon_0d_input():
     # Bug reported in #4938: 0d input causes segfault.
     rr, cc = polygon(0, 1)
 
-    assert rr.size == cc.size == 0
+    assert rr.size == cc.size == 1
 
 
 def test_circle_deprecated():
diff --git a/skimage/draw/tests/test_polygon2mask.py b/skimage/draw/tests/test_polygon2mask.py
--- a/skimage/draw/tests/test_polygon2mask.py
+++ b/skimage/draw/tests/test_polygon2mask.py
@@ -11,4 +11,4 @@
 def test_polygon2mask():
     mask = draw.polygon2mask(image_shape, polygon)
     assert mask.shape == image_shape
-    assert mask.sum() == 57647
+    assert mask.sum() == 57653
diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py
--- a/skimage/measure/tests/test_regionprops.py
+++ b/skimage/measure/tests/test_regionprops.py
@@ -151,13 +151,11 @@ def test_centroid_3d():
 
 def test_convex_area():
     area = regionprops(SAMPLE)[0].convex_area
-    # determined with MATLAB
-    assert area == 124
+    assert area == 125
 
 
 def test_convex_image():
     img = regionprops(SAMPLE)[0].convex_image
-    # determined with MATLAB
     ref = np.array(
         [[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
          [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
@@ -167,7 +165,7 @@ def test_convex_image():
          [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
          [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
          [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
-         [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
+         [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
          [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
     )
     assert_array_equal(img, ref)
@@ -387,8 +385,7 @@ def test_perimeter_crofton():
 
 def test_solidity():
     solidity = regionprops(SAMPLE)[0].solidity
-    # determined with MATLAB
-    assert_almost_equal(solidity, 0.580645161290323)
+    assert_almost_equal(solidity, 0.576)
 
 
 def test_weighted_moments_central():
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
@@ -39,12 +39,12 @@ def test_qhull_offset_example():
                   1371, 1371, 1371, 1371, 1371, 1372, 1372, 1372, 1372, 1372,
                   1372, 1372, 1372, 1372, 1373, 1373, 1373, 1373, 1373, 1373,
                   1373, 1373, 1373, 1374, 1374, 1374, 1374, 1374, 1374, 1374,
-                  1375, 1375, 1375, 1375, 1375, 1376, 1376, 1376, 1377]),
+                  1375, 1375, 1375, 1375, 1375, 1376, 1376, 1376, 1377, 1372]),
                 ([151, 150, 151, 152, 149, 150, 151, 152, 153, 148, 149, 150,
                  151, 152, 153, 154, 147, 148, 149, 150, 151, 152, 153, 154,
                  155, 146, 147, 148, 149, 150, 151, 152, 153, 154, 146, 147,
                  148, 149, 150, 151, 152, 153, 154, 147, 148, 149, 150, 151,
-                 152, 153, 148, 149, 150, 151, 152, 149, 150, 151, 150]))
+                  152, 153, 148, 149, 150, 151, 152, 149, 150, 151, 150, 155]))
     image = np.zeros((1392, 1040), dtype=bool)
     image[nonzeros] = True
     expected = image.copy()
@@ -59,7 +59,7 @@ def test_pathological_qhull_example():
     expected = np.array(
                 [[0, 0, 0, 1, 1, 1, 0],
                  [0, 1, 1, 1, 1, 1, 1],
-                 [1, 1, 1, 1, 0, 0, 0]], dtype=bool)
+                 [1, 1, 1, 1, 1, 0, 0]], dtype=bool)
     assert_array_equal(convex_hull_image(image), expected)
 
 
diff --git a/skimage/viewer/tests/test_plugins.py b/skimage/viewer/tests/test_plugins.py
--- a/skimage/viewer/tests/test_plugins.py
+++ b/skimage/viewer/tests/test_plugins.py
@@ -43,7 +43,7 @@ def test_line_profile_rgb():
     for i in range(6):
         plugin.line_tool._thicken_scan_line()
     line_image, scan_data = plugin.output()
-    assert_equal(line_image[line_image == 128].size, 750)
+    assert_equal(line_image[line_image == 128].size, 906)
     assert_equal(line_image[line_image == 255].size, 151)
     assert_equal(line_image.shape, (300, 451))
     assert_equal(scan_data.shape, (151, 3))