Repository: scikit-image/scikit-image

Issue:
exposure.equalize_adapthist changes output datatype
* **[ORIGINAL TITLE] Setting clip-limit to 1 does not return original image.**

## Description
In the _clahe function, it says setting the clip limit to one will return the original image. That is not true because before it is passed through to that function, clip limit is multiplied by "nbins". 



Discussion:
Hi @rdaniels5 ,
thank you for opening this issue! Could you please send us some code exemplifying it? We will try to reproduce based on that.
Thanks again!
    import matplotlib.image as mpimg
    from skimage.exposure import equalize_adapthist

    img = mpimg.imread('hist_eq.png')
    start = time()
    img_t = torch.tensor(img)
    new = equalize_adapthist(img_t, clip_limit=1, kernel_size=(32, 32))
Hey @rdaniels5,
I'm sorry, I haven't expressed myself very clearly :) could you please send us a _fully reproducible_ example, with the image you used, if possible?
Thank you again!
````
from skimage import data, exposure
import matplotlib.pyplot as plt

plt.imshow(exposure.equalize_adapthist(data.moon(), clip_limit=1))
````

Is this good enough? It does the same thing with this code.
Hey @rdaniels5,
thank you for the example!
I checked it here, and it actually does return the original image back. It indeed [multiplies `clip_limit` by nbins](https://github.com/scikit-image/scikit-image/blob/786adad0da9b399c9c4689bf43b44b95011c13ac/skimage/exposure/_adapthist.py#L85), but before that, it [converts the image to `uint`](https://github.com/scikit-image/scikit-image/blob/786adad0da9b399c9c4689bf43b44b95011c13ac/skimage/exposure/_adapthist.py#L73) — currently necessary for the algorithm to work. I think your issues come because the algorithm [returns a float image](https://github.com/scikit-image/scikit-image/blob/786adad0da9b399c9c4689bf43b44b95011c13ac/skimage/exposure/_adapthist.py#L86) no matter what input it had, modifying the original image.
One small example showing they are indeed the same, with a different data type:
```
from skimage import data, exposure, util
import numpy as np

image = data.moon()  # dtype('uint8')
image_clahe = exposure.equalize_adapthist(image, clip_limit=1)  # dtype('float64')
image_clahe_uint8 = util.img_as_ubyte(image)  # dtype('uint8')
np.array_equal(image, image_clahe_uint8)
```
In this case, `np.array_equal` returns `True`, indicating the arrays have the same values.
Thank you for pointing that out! Let's see what we can do to solve this issue.
@jni some context: is there a way to retrieve the input image datatype and return the same dtype on the output image, for `exposure.equalize_adapthist`? Thank you!
A simpler solution would be to check for `clip_limit == 1` in `equalize_adapthist` before converting to `uint`...

Resolving patch:
diff --git a/skimage/exposure/_adapthist.py b/skimage/exposure/_adapthist.py
--- a/skimage/exposure/_adapthist.py
+++ b/skimage/exposure/_adapthist.py
@@ -51,7 +51,7 @@ def equalize_adapthist(image, kernel_size=None,
     Returns
     -------
     out : (M, N[, C]) ndarray
-        Equalized image.
+        Equalized image with float64 dtype.
 
     See Also
     --------
@@ -70,6 +70,10 @@ def equalize_adapthist(image, kernel_size=None,
     .. [1] http://tog.acm.org/resources/GraphicsGems/
     .. [2] https://en.wikipedia.org/wiki/CLAHE#CLAHE
     """
+
+    if clip_limit == 1.0:
+        return img_as_float(image)  # convert to float for consistency
+
     image = img_as_uint(image)
     image = rescale_intensity(image, out_range=(0, NR_OF_GRAY - 1))
 
@@ -82,7 +86,7 @@ def equalize_adapthist(image, kernel_size=None,
 
     kernel_size = [int(k) for k in kernel_size]
 
-    image = _clahe(image, kernel_size, clip_limit * nbins, nbins)
+    image = _clahe(image, kernel_size, clip_limit, nbins)
     image = img_as_float(image)
     return rescale_intensity(image)
 
@@ -112,12 +116,6 @@ def _clahe(image, kernel_size, clip_limit, nbins):
     minimum and maximum value as the input image. A clip limit smaller than 1
     results in standard (non-contrast limited) AHE.
     """
-
-    if clip_limit == 1.0:
-        return image  # is OK, immediately returns original image.
-
-    clip_limit /= nbins
-
     row_step = int(image.shape[0] // np.ceil(image.shape[0] / kernel_size[0]))
     col_step = int(image.shape[1] // np.ceil(image.shape[1] / kernel_size[1]))
 


Test patch:
diff --git a/skimage/exposure/tests/test_exposure.py b/skimage/exposure/tests/test_exposure.py
--- a/skimage/exposure/tests/test_exposure.py
+++ b/skimage/exposure/tests/test_exposure.py
@@ -374,6 +374,19 @@ def test_adapthist_borders():
     assert norm_brightness_err(adapted[-width, :], img[-width, :]) > 1e-3
 
 
+def test_adapthist_clip_limit():
+    img_u = data.moon()
+    img_f = util.img_as_float(img_u)
+
+    # uint8 input
+    img_clahe = exposure.equalize_adapthist(img_u, clip_limit=1)
+    assert_array_equal(img_f, img_clahe)
+
+    # float64 input
+    img_clahe = exposure.equalize_adapthist(img_f, clip_limit=1)
+    assert_array_equal(img_f, img_clahe)
+
+
 def peak_snr(img1, img2):
     """Peak signal to noise ratio of two images