Repository: scikit-image/scikit-image

Issue:
ORB detection doesn't work on 32 bit float input
## Description

Trying to use ORB feature detection of an image of dtype 'f4' fails, as the function [_corner_fast](https://github.com/scikit-image/scikit-image/blob/master/skimage/feature/corner_cy.pyx#L110) requires `double` input, but nothing converts it to double on the way. Integer input works however.

## Way to reproduce
```python
from skimage import data, feature

img = data.checkerboard().astype('f4')
orb = feature.ORB()
orb.detect(img)
```


## Version information

```python
3.8.2 (default, Feb 28 2020, 00:00:00) 
[GCC 10.0.1 20200216 (Red Hat 10.0.1-0.8)]
Linux-5.6.8-300.fc32.x86_64-x86_64-with-glibc2.2.5
scikit-image version: 0.17.1
numpy version: 1.18.4

```



Discussion:
Thank you @SuborbitalPigeon for reporting.
I suspect that this comes from Cython version change. I think that previously, any input was implicitly converted to `double` (which is already bad).
`np_floats` fused type use must be generalized.
`BRIEF` is also affected:
```python
>>> from skimage import data
>>> from skimage.feature import (corner_peaks, corner_harris, BRIEF)
>>> from skimage.color import rgb2gray
>>> img1 = rgb2gray(data.astronaut())
>>> keypoints1 = corner_peaks(corner_harris(img1), min_distance=5)
__main__:1: FutureWarning: Until version 0.16, threshold_rel was set to 0.1 by default. Starting from version 0.16, the default value is set to None. Until version 0.18, a None value corresponds to a threshold value of 0.1. The default behavior will match skimage.feature.peak_local_max. To avoid this warning, set threshold_rel=0.
>>> extractor = BRIEF()
>>> extractor.extract(img1.astype('f4'), keypoints1)
...
ValueError: Buffer dtype mismatch, expected 'double' but got 'float'
```
And, I imagine, all the functions declaring a buffer of `double` in their signature are also affected.
Segmentation on `float32` inputs doesn't work either:

```python
from skimage.segmentation import slic
import numpy as np
im = np.random.rand(28, 28).astype(np.float32)
slic(im)

skimage.measure.label's indexing starts from 0. In future version it will start from 1. To disable this warning, explicitely set the `start_label` parameter to 1.
Traceback (most recent call last):
  File "/home/janis/.conda/envs/py37/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 3319, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-61-1f8b517e73c1>", line 1, in <module>
    slic(im)
  File "/home/janis/.conda/envs/py37/lib/python3.7/site-packages/skimage/segmentation/slic_superpixels.py", line 298, in slic
    start_label=start_label)
  File "skimage/segmentation/_slic.pyx", line 21, in skimage.segmentation._slic._slic_cython
ValueError: Buffer dtype mismatch, expected 'float64_t' but got 'float'
```
Thank you @jklaise for the report. May be should we open separate Issues for each affected function to keep track of each of them...
@rfezzani sounds like this could affect most of the library. Is it the case that pre-0.17 most functions supported `float32` by implicitly casting to `float64` and some others supported it natively?
In fact, it seems that many cython functions will be affected :confused:
My hypothesis is that cython stopped implicitly supporting `float32` dtype when double precision arrays are in function's signature... That's the right thing to do IMHO, despite that it will introduce a lot of breaking changes in `skimage`...
Another potential cause is the change to `image_as_float` made in #3110 to preserves single precision inputs rather than automatically promoting them to double. However, that change happened a couple releases prior (either 0.14 or 0.15), so probably does not explain the issue if it only recently started occurring.
Well, I checked, and it seems that this bug already existed in v0.16:
```python
>>> import skimage
>>> skimage.__version__
'0.16.2'
>>> from skimage import data, feature
>>> 
>>> img = data.checkerboard().astype('f4')
>>> orb = feature.ORB()
>>> orb.detect(img)
...
File "skimage/feature/corner_cy.pyx", line 110, in skimage.feature.corner_cy._corner_fast
ValueError: Buffer dtype mismatch, expected 'double' but got 'float'
```
My hypothesis was all wrong, sorry...
@jklaise, I also checked for `slic`, v0.16 is also affected
```python
>>> from skimage.segmentation import slic
>>> import numpy as np
>>> im = np.random.rand(28, 28).astype(np.float32)
>>> slic(im)
...
  File "skimage/segmentation/_slic.pyx", line 13, in skimage.segmentation._slic._slic_cython
ValueError: Buffer dtype mismatch, expected 'double' but got 'float'
>>> skimage.__version__
'0.16.2'
```

Resolving patch:
diff --git a/skimage/feature/orb.py b/skimage/feature/orb.py
--- a/skimage/feature/orb.py
+++ b/skimage/feature/orb.py
@@ -188,7 +188,8 @@ def detect(self, image):
             keypoints_list.append(keypoints * self.downscale ** octave)
             orientations_list.append(orientations)
             scales_list.append(np.full(
-                keypoints.shape[0], self.downscale ** octave, dtype='float64'))
+                keypoints.shape[0], self.downscale ** octave,
+                dtype=octave_image.dtype))
             responses_list.append(responses)
 
         keypoints = np.vstack(keypoints_list)


Test patch:
diff --git a/skimage/feature/tests/test_orb.py b/skimage/feature/tests/test_orb.py
--- a/skimage/feature/tests/test_orb.py
+++ b/skimage/feature/tests/test_orb.py
@@ -1,4 +1,6 @@
+import pytest
 import numpy as np
+
 from skimage._shared.testing import assert_equal, assert_almost_equal
 from skimage.feature import ORB
 from skimage._shared import testing
@@ -120,3 +122,10 @@ def test_no_descriptors_extracted_orb():
     detector_extractor = ORB()
     with testing.raises(RuntimeError):
         detector_extractor.detect_and_extract(img)
+
+
+@pytest.mark.parametrize('dtype', ['float32', 'float64', 'uint8', 'int'])
+def test_dtype_support(dtype):
+    img = data.checkerboard().astype(dtype)
+    orb = ORB()
+    orb.detect(img)