In [1]:
from PIL import Image
from IPython.display import display
from io import BytesIO
import urllib
import requests
import numpy as np
import matplotlib

Some utility functions we need later on

In [2]:
# Display ndarray as image using matplotlib
def display_img(img_data):
    img = Image.fromarray(img_data)
    display(img)

def convert_uint_to_float(img_data):
    return img_data / 255
    
def convert_float_to_uint(img_data):
    return round_to_uint(img_data * 255)

def round_to_uint(img_data):
    return np.round(img_data).astype('uint8')

# A utility function to convert hex string to a tuple of RGB
def hex_to_rgb(hex_string):
    return np.array(list(int(hex_string.lstrip('#')[i:i+2], 16) for i in (0, 2, 4)))

def hex_to_rgb_float(hex_string):
    return np.array(list((int(hex_string.lstrip('#')[i:i+2], 16) / 255) for i in (0, 2, 4)))

def get_array_from_hex(hex_string, height, width):
    rgb_as_float = hex_to_rgb_float(hex_string)
    return np.full((height, width, 3), rgb_as_float)
In [3]:
# Unused code at the moment, but may use later
def rgb_to_hsl_arr(image_data):
    return np.apply_along_axis(rgb_to_hsl, -1, image_data)

def hsl_to_rgb_arr(image_data):
    return np.apply_along_axis(hsl_to_rgb, -1, image_data)

def rgb_to_hsl(rgb_as_float):
    r, g, b = rgb_as_float
    high = max(r, g, b)
    low = min(r, g, b)
    h, s, v = ((high + low) / 2,)*3

    if high == low:
        h = 0.0
        s = 0.0
    else:
        d = high - low
        l = (high + low) / 2
        s = d / (2 - high - low) if l > 0.5 else d / (high + low)
        h = {
            r: (g - b) / d + (6 if g < b else 0),
            g: (b - r) / d + 2,
            b: (r - g) / d + 4,
        }[high]
        h /= 6

    return [h, s, v]

def hsl_to_rgb(hsl_as_float):
    h, s, l = hsl_as_float
    
    def hue_to_rgb(p, q, t):
        t += 1 if t < 0 else 0
        t -= 1 if t > 1 else 0
        if t < 1/6: return p + (q - p) * 6 * t
        if t < 1/2: return q
        if t < 2/3: p + (q - p) * (2/3 - t) * 6
        return p

    if s == 0:
        r, g, b = l, l, l
    else:
        q = l * (1 + s) if l < 0.5 else l + s - l * s
        p = 2 * l - q
        r = hue_to_rgb(p, q, h + 1/3)
        g = hue_to_rgb(p, q, h)
        b = hue_to_rgb(p, q, h - 1/3)

    return [r, g, b]
In [4]:
# Code from https://github.com/lukexyz/CV-Instagram-Filters
def split_image_into_channels(image):
    """Look at each image separately"""
    red_channel = image[:, :, 0]
    green_channel = image[:, :, 1]
    blue_channel = image[:, :, 2]
    return red_channel, green_channel, blue_channel


def merge_channels(red, green, blue):
    """Merge channels back into an image"""
    return np.stack([red, green, blue], axis=2)

def channel_adjust(channel, values):
    # preserve the original size, so we can reconstruct at the end
    orig_size = channel.shape
    # flatten the image into a single array
    flat_channel = channel.flatten()

    # this magical numpy function takes the values in flat_channel
    # and maps it from its range in [0, 1] to its new squeezed and
    # stretched range
    adjusted = np.interp(flat_channel, np.linspace(0, 1, len(values)), values)

    # put back into the original image shape
    return adjusted.reshape(orig_size)

Load and display an image file

In [5]:
image01_url = "https://github.com/subwaymatch/layer-is-python/blob/master/images/image02.jpg?raw=true"
response = requests.get(image01_url)
image01 = Image.open(BytesIO(response.content))

display(image01)

PIL to Numpy conversions

Read an image as a Numpy ndarray

In [6]:
image01_data = np.asarray(image01)
type(image01_data)
Out[6]:
numpy.ndarray

Numpy array into PIL image

In [7]:
img = Image.fromarray(image01_data, 'RGB')
# display(img)

Numpy array of unsigned int pixel values to float values (0 ~ 1)

In [8]:
image01_float_data = convert_uint_to_float(image01_data)

Convert image to grayscale

In [9]:
image01_gray = np.dot(image01_data[...,:3], [0.2989, 0.5870, 0.1140])
image01_gray_data = np.stack((round_to_uint(image01_gray),)*3, axis=-1)
In [10]:
display_img(image01_gray_data)