Repository: scikit-image/scikit-image

Issue:
uint8 overflow in exposure.adjust_gamma
# Description

When using the function *skimage.exposure.adjust_gamma* with an uint8 image with a gain superior to 1, the integers can overflow and return false results. A value bigger than 255 should be set to 255.

# Way to reproduce

```python
from skimage.exposure import adjust_gamma
import numpy as np

img = 255 * np.ones((3, 3), dtype=np.uint8)
transformed_img = adjust_gamma(img, gamma=1, gain=1.1)
print(transformed_img[0, 0])
```

This returns a value of `24` which is wrong.

The problem is in the definition of the lut in the  function `_adjust_gamma_u8` (l443):
```python
    lut = (255 * gain * (np.linspace(0, 1, 256) ** gamma)).astype('uint8')
```
Before recasting as 'uint8' a max(255, lut) should probably be applied.

 # 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("scikit-image version: {}".format(skimage.__version__))
import numpy; print("numpy version: {}".format(numpy.__version__))
 ```
 ```
 3.9.5 (default, May 18 2021, 19:34:48) 
[GCC 7.3.0]
Linux-4.15.0-143-generic-x86_64-with-glibc2.27
scikit-image version: 0.18.1
numpy version: 1.20.2
 ```

 



Discussion:
Thank you for the very detailed and complete bug report.

Would you be willing to provide the pull request to fix this issue?

I would suggest you include your failing example as a test and assert that the result is correct.
Yes, I can do the PR (probably this week-end) 

Resolving patch:
diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py
--- a/skimage/exposure/exposure.py
+++ b/skimage/exposure/exposure.py
@@ -437,10 +437,11 @@ def _assert_non_negative(image):
 
 
 def _adjust_gamma_u8(image, gamma, gain):
-    """LUT based implmentation of gamma adjustement.
+    """LUT based implementation of gamma adjustment.
 
     """
-    lut = (255 * gain * (np.linspace(0, 1, 256) ** gamma)).astype('uint8')
+    lut = (255 * gain * (np.linspace(0, 1, 256) ** gamma))
+    lut = np.minimum(lut, 255).astype('uint8')
     return lut[image]
 
 


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
@@ -592,12 +592,18 @@ def test_adjust_gamma_greater_one():
     assert_array_equal(result, expected)
 
 
-def test_adjust_gamma_neggative():
+def test_adjust_gamma_negative():
     image = np.arange(0, 255, 4, np.uint8).reshape((8, 8))
     with testing.raises(ValueError):
         exposure.adjust_gamma(image, -1)
 
 
+def test_adjust_gamma_u8_overflow():
+    img = 255 * np.ones((2, 2), dtype=np.uint8)
+
+    assert np.all(exposure.adjust_gamma(img, gamma=1, gain=1.1) == 255)
+
+
 # Test Logarithmic Correction
 # ===========================
 
@@ -726,12 +732,6 @@ def test_adjust_inv_sigmoid_cutoff_half():
     assert_array_equal(result, expected)
 
 
-def test_negative():
-    image = np.arange(-10, 245, 4).reshape((8, 8)).astype(np.double)
-    with testing.raises(ValueError):
-        exposure.adjust_gamma(image)
-
-
 def test_is_low_contrast():
     image = np.linspace(0, 0.04, 100)
     assert exposure.is_low_contrast(image)
@@ -756,6 +756,18 @@ def test_is_low_contrast_boolean():
     assert not exposure.is_low_contrast(image)
 
 
+# Test negative input
+#####################
+
+@pytest.mark.parametrize("exposure_func", [exposure.adjust_gamma,
+                                           exposure.adjust_log,
+                                           exposure.adjust_sigmoid])
+def test_negative_input(exposure_func):
+    image = np.arange(-10, 245, 4).reshape((8, 8)).astype(np.double)
+    with testing.raises(ValueError):
+        exposure_func(image)
+
+
 # Test Dask Compatibility
 # =======================