# Stdlib imports
from io import BytesIO

# Third-party libraries
from IPython.display import Image
from ipywidgets import interact, interactive, fixed
import matplotlib as mpl
from skimage import data, filters, io, img_as_float
import numpy as np

"""
Explanation: Image Manipulation with skimage
This example builds a simple UI for performing basic image manipulation with scikit-image.
End of explanation
"""


i = img_as_float(data.coffee())
i.shape

"""
Explanation: Let's load an image from scikit-image's collection, stored in the data module. These come back as regular numpy arrays:
End of explanation
"""


def arr2img(arr):
    """Display a 2- or 3-d numpy array as an image."""
    if arr.ndim == 2:
        format, cmap = 'png', mpl.cm.gray
    elif arr.ndim == 3:
        format, cmap = 'jpg', None
    else:
        raise ValueError("Only 2- or 3-d arrays can be displayed as images.")
    # Don't let matplotlib autoscale the color range so we can control overall luminosity
    vmax = 255 if arr.dtype == 'uint8' else 1.0
    with BytesIO() as buffer:
        mpl.image.imsave(buffer, arr, format=format, cmap=cmap, vmin=0, vmax=vmax)
        out = buffer.getvalue()
    return Image(out)

arr2img(i)

"""
Explanation: Let's make a little utility function for displaying Numpy arrays with the IPython display protocol:
End of explanation
"""


def edit_image(image, sigma=0.1, R=1.0, G=1.0, B=1.0):
    new_image = filters.gaussian_filter(image, sigma=sigma, multichannel=True)
    new_image[:,:,0] = R*new_image[:,:,0]
    new_image[:,:,1] = G*new_image[:,:,1]
    new_image[:,:,2] = B*new_image[:,:,2]
    return arr2img(new_image)

"""
Explanation: Now, let's create a simple "image editor" function, that allows us to blur the image or change its color balance:
End of explanation
"""


edit_image(i, sigma=5, R=0.1)

"""
Explanation: We can call this function manually and get a new image. For example, let's do a little blurring and remove all the red from the image:
End of explanation
"""


lims = (0.0,1.0,0.01)
interact(edit_image, image=fixed(i), sigma=(0.0,10.0,0.1), R=lims, G=lims, B=lims);

"""
Explanation: But it's a lot easier to explore what this function does by controlling each parameter interactively and getting immediate visual feedback. IPython's ipywidgets package lets us do that with a minimal amount of code:
End of explanation
"""


def choose_img(name):
    # Let's store the result in the global `img` that we can then use in our image editor below
    global img
    img = getattr(data, name)()
    return arr2img(img)

# Skip 'load' and 'lena', two functions that don't actually return images
interact(choose_img, name=sorted(set(data.__all__)-{'lena', 'load'}));

"""
Explanation: Browsing the scikit-image gallery, and editing grayscale and jpg images
The coffee cup isn't the only image that ships with scikit-image, the data module has others. Let's make a quick interactive explorer for this:
End of explanation
"""


lims = (0.0, 1.0, 0.01)

def edit_image(image, sigma, R, G, B):
    new_image = filters.gaussian_filter(image, sigma=sigma, multichannel=True)
    if new_image.ndim == 3:
        new_image[:,:,0] = R*new_image[:,:,0]
        new_image[:,:,1] = G*new_image[:,:,1]
        new_image[:,:,2] = B*new_image[:,:,2]
    else:
        new_image = G*new_image
    return arr2img(new_image)

interact(edit_image, image=fixed(img), sigma=(0.0, 10.0, 0.1), 
               R=lims, G=lims, B=lims);

"""
Explanation: And now, let's update our editor to cope correctly with grayscale and color images, since some images in the scikit-image collection are grayscale.  For these, we ignore the red (R) and blue (B) channels, and treat 'G' as 'Grayscale':
End of explanation
"""


lims = (0.0, 1.0, 0.01)

@interact
def edit_image(image: fixed(img), σ:(0.0, 10.0, 0.1)=0, 
               R:lims=1.0, G:lims=1.0, B:lims=1.0):
    new_image = filters.gaussian_filter(image, sigma=σ, multichannel=True)
    if new_image.ndim == 3:
        new_image[:,:,0] = R*new_image[:,:,0]
        new_image[:,:,1] = G*new_image[:,:,1]
        new_image[:,:,2] = B*new_image[:,:,2]
    else:
        new_image = G*new_image
    return arr2img(new_image)

"""
Explanation: Python 3 only: Function annotations and unicode identifiers
In Python 3, we can use the new function annotation syntax to describe widgets for interact, as well as unicode names for variables such as sigma. Note how this syntax also lets us define default values for each control in a convenient (if slightly awkward looking) form: var:descriptor=default.
End of explanation
"""


