Repository: scikit-image/scikit-image

Issue:
[performance] Unwanted cast from float32 to double in slic
## Description

If a `float32` image is given to  `skimage.segmentation.slic`, it will be cast to `double` implicitly.

In fact, in `slic`, the Cython function `_slic_cython` is called with the input image preprocessed with `img_as_float`. If the input array data type is `float32`, it is not touched.
Since the `_slic_cython` signature is 
```
    (double[:, :, :, ::1] image_zyx,
     double[:, ::1] segments,
     float step,
     Py_ssize_t max_iter,
     double[::1] spacing,
     bint slic_zero)
```
the input image is automatically and silently cast to `double`.

Using the `np_floats` fused type as advised in #3111 solves the problem.



Discussion:

Resolving patch:
diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py
--- a/skimage/segmentation/slic_superpixels.py
+++ b/skimage/segmentation/slic_superpixels.py
@@ -109,6 +109,8 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
     """
 
     image = img_as_float(image)
+    dtype = image.dtype
+
     is_2d = False
     if image.ndim == 2:
         # 2D grayscale image
@@ -123,15 +125,15 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
         image = image[..., np.newaxis]
 
     if spacing is None:
-        spacing = np.ones(3)
+        spacing = np.ones(3, dtype=dtype)
     elif isinstance(spacing, (list, tuple)):
-        spacing = np.array(spacing, dtype=np.double)
+        spacing = np.array(spacing, dtype=dtype)
 
     if not isinstance(sigma, Iterable):
-        sigma = np.array([sigma, sigma, sigma], dtype=np.double)
-        sigma /= spacing.astype(np.double)
+        sigma = np.array([sigma, sigma, sigma], dtype=dtype)
+        sigma /= spacing.astype(dtype)
     elif isinstance(sigma, (list, tuple)):
-        sigma = np.array(sigma, dtype=np.double)
+        sigma = np.array(sigma, dtype=dtype)
     if (sigma > 0).any():
         # add zero smoothing for multichannel dimension
         sigma = list(sigma) + [0]
@@ -146,7 +148,10 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
     depth, height, width = image.shape[:3]
 
     # initialize cluster centroids for desired number of segments
-    grid_z, grid_y, grid_x = np.mgrid[:depth, :height, :width]
+    grid_z, grid_y, grid_x = np.meshgrid(np.arange(depth, dtype=dtype),
+                                         np.arange(height, dtype=dtype),
+                                         np.arange(width, dtype=dtype),
+                                         indexing='ij')
     slices = regular_grid(image.shape[:3], n_segments)
     step_z, step_y, step_x = [int(s.step if s.step is not None else 1)
                               for s in slices]
@@ -154,7 +159,8 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
     segments_y = grid_y[slices]
     segments_x = grid_x[slices]
 
-    segments_color = np.zeros(segments_z.shape + (image.shape[3],))
+    segments_color = np.zeros(segments_z.shape + (image.shape[3],),
+                              dtype=dtype)
     segments = np.concatenate([segments_z[..., np.newaxis],
                                segments_y[..., np.newaxis],
                                segments_x[..., np.newaxis],
@@ -164,8 +170,8 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
 
     # we do the scaling of ratio in the same way as in the SLIC paper
     # so the values have the same meaning
-    step = float(max((step_z, step_y, step_x)))
-    ratio = 1.0 / compactness
+    step = dtype.type(max((step_z, step_y, step_x)))
+    ratio = dtype.type(1.0 / compactness)
 
     image = np.ascontiguousarray(image * ratio)
 


Test patch: