Repository: scikit-image/scikit-image

Issue:
numpy1.22rc0 breaks skimage.filters.rank_order

## Way to reproduce

## Description

In `rank_order`, there is a call to `np.cumsum` like this:

```Python
np.cumsum(is_different, out=sort_rank[1:])
```

where the input, `is_different` has dtype `bool` and the output array, `sort_rank` had dtype `np.uint32`. With NumPy 1.22rc0, the maximum value appearing in `out` ends up being 1, which seems like it is truncating to the range of the input dtype. I assume this is a NumPy bug, and will look into reporting it upstream if there is not already an issue open for it.

A temporary workaround is just to explicitly specify the dtype

```Python
np.cumsum(is_different, out=sort_rank[1:], dtype=sort_rank.dtype)
```

I will make a PR with the workaround for now, but ultimately it seems like a fix is probably needed upstream.


## Version information
```python
# Paste the output of the following python commands
from __future__ import print_function
import sys; print(sys.version)
import platform; print(platform.platform())
import skimage; print(f'scikit-image version: {skimage.__version__}')
import numpy; print(f'numpy version: {numpy.__version__}')
```

```python
# your output here

```



Discussion:
I think numpy will be sympathetic, but from previous issues I've seen with them, their opinion was that "sums" should not be taken on booleans.

In fact, certain fields of mathematics would think that `1 + 1 = 0` in  boolean where `sum` acts more like an `xor`.

I would wager that we should do `.as_type('uint8')` before running the sum.
Hmm ok, i was mostly worried about this:

```
>>> import numpy as np
>>> a = np.arange(8, dtype='uint8')
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7], dtype=uint8)
>>> a.view('bool')
array([False,  True,  True,  True,  True,  True,  True,  True])
>>> np.cumsum(a.view('bool'), dtype='int32')
array([0, 1, 2, 3, 4, 5, 6, 7], dtype=int32)
```
but it seems to work as intended


I opened an upstream issue: https://github.com/numpy/numpy/issues/20455

> but it seems to work as intended

This uint8 case you gave works for that small sum, but not if the sum exceeds 255! See example at: https://github.com/numpy/numpy/issues/20455#issuecomment-978049481
Hmm i guess i meant to say "in addition to" specifying the output dtype, you should also convert to an more standard integer type, but I think that your fix is correct in entirety.
Ah, yes, my example wasn't quite equivalent to yours, which had specified the dtype. It does show other unexpected behavior, though.

There is no penalty to explicitly specifying dtype on older versions, so we can just do that for now. 

Resolving patch:
diff --git a/skimage/filters/_rank_order.py b/skimage/filters/_rank_order.py
--- a/skimage/filters/_rank_order.py
+++ b/skimage/filters/_rank_order.py
@@ -50,7 +50,7 @@ def rank_order(image):
     flat_image = flat_image[sort_order]
     sort_rank = np.zeros_like(sort_order)
     is_different = flat_image[:-1] != flat_image[1:]
-    np.cumsum(is_different, out=sort_rank[1:])
+    np.cumsum(is_different, out=sort_rank[1:], dtype=sort_rank.dtype)
     original_values = np.zeros((sort_rank[-1] + 1,), image.dtype)
     original_values[0] = flat_image[0]
     original_values[1:] = flat_image[1:][is_different]


Test patch:
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
@@ -3,8 +3,9 @@
 import numpy as np
 import pytest
 import scipy.ndimage as ndi
-from numpy.testing import (assert_almost_equal, assert_array_almost_equal,
-                           assert_array_equal, assert_equal)
+from numpy.testing import (assert_allclose, assert_almost_equal,
+                           assert_array_almost_equal, assert_array_equal,
+                           assert_equal)
 
 from skimage import data, draw, transform
 from skimage._shared._warnings import expected_warnings
@@ -762,7 +763,8 @@ def test_multichannel():
         else:
             # property uses multiple channels, returns props stacked along
             # final axis
-            assert_array_equal(p, np.asarray(p_multi)[..., 1])
+            assert_allclose(p, np.asarray(p_multi)[..., 1], rtol=1e-12,
+                            atol=1e-12)
 
 
 def test_3d_ellipsoid_axis_lengths():