Commit e6bd581c authored by Bryce Hepner's avatar Bryce Hepner
Browse files

Runs much longer than the other

parent 36d6711f
Loading
Loading
Loading
Loading
+480 −0
Original line number Diff line number Diff line
%% Cell type:code id: tags:

``` python
import numpy as np
from matplotlib import pyplot as plt
import os
import sys
from PIL import Image
from collections import Counter
import numpy.linalg as la
```

%% Cell type:code id: tags:

``` python
def file_extractor(dirname="images"):
    files = os.listdir(dirname)
    scenes = []
    for file in files:
        if file == '.DS_Store':
            continue
        else:
            scenes.append(os.path.join(dirname, file))
    return scenes

def image_extractor(scenes):
    image_folder = []
    for scene in scenes:
        files = os.listdir(scene)
        for file in files:
            if file[-5:] != ".tiff" or file[-7:] == "_6.tiff":
                continue
            else:
                image_folder.append(os.path.join(scene, file))
    return image_folder #returns a list of file paths to .tiff files in the specified directory given in file_extractor
```

%% Cell type:code id: tags:

``` python
def predict_pix(tiff_image_path, difference = True):
    """
    This function predict the pixel values excluding the boundary.
    Using the 4 neighbor pixel values and MSE to predict the next pixel value
    (-1,1) (0,1) (1,1)  => relative position of the 4 other given values
    (-1,0) (0,0)        => (0,0) is the one we want to predict
    take the derivative of mean square error to solve for the system of equation
    A = np.array([[3,0,-1],[0,3,3],[1,-3,-4]])
    A @ [a, b, c] = [-z0+z2-z3, z0+z1+z2, -z0-z1-z2-z3] where z0 = (-1,1), z1 = (0,1), z2 = (1,1), z3 = (-1,0)
    and the predicted pixel value is c.

    Input:
    tiff_image_path (string): path to the tiff file

    Return:
    image   ndarray(512 X 640): original image
    predict ndarray(325380,): predicted image excluding the boundary
    diff.   ndarray(325380,): IF difference = TRUE, difference between the min and max of four neighbors exclude the boundary
                            ELSE: the residuals of the four nearest pixels to a fitted hyperplane
    error   ndarray(325380,): difference between the original image and predicted image
    A       ndarray(3 X 3): system of equation
    """
    image_obj = Image.open(tiff_image_path)    #Open the image and read it as an Image object
    image_array = np.array(image_obj)[1:,:].astype(int)    #Convert to an array, leaving out the first row because the first row is just housekeeping data
    # image_array = image_array.astype(int)
    A = np.array([[3,0,-1],[0,3,3],[1,-3,-4]]) # the matrix for system of equation
    # where z0 = (-1,1), z1 = (0,1), z2 = (1,1), z3 = (-1,0)
    z0 = image_array[0:-2,0:-2]   # get all the first pixel for the entire image
    z1 = image_array[0:-2,1:-1]   # get all the second pixel for the entire image
    z2 = image_array[0:-2,2::]    # get all the third pixel for the entire image
    z3 = image_array[1:-1,0:-2]   # get all the forth pixel for the entire image

    # calculate the out put of the system of equation
    y0 = np.ravel(-z0+z2-z3)
    y1 = np.ravel(z0+z1+z2)
    y2 = np.ravel(-z0-z1-z2-z3)
    y = np.vstack((y0,y1,y2))

    # use numpy solver to solve the system of equations all at once
    #predict = np.floor(np.linalg.solve(A,y)[-1])
    predict = np.round(np.round((np.linalg.solve(A,y)[-1]),1))

    #Matrix system of points that will be used to solve the least squares fitting hyperplane
    points = np.array([[-1,-1,1], [-1,0,1], [-1,1,1], [0,-1,1]])

    # flatten the neighbor pixlels and stack them together
    z0 = np.ravel(z0)
    z1 = np.ravel(z1)
    z2 = np.ravel(z2)
    z3 = np.ravel(z3)
    neighbor = np.vstack((z0,z1,z2,z3)).T

    if difference:
        # calculate the difference
        diff = np.max(neighbor,axis = 1) - np.min(neighbor, axis=1)

    else:
        #Compute the best fitting hyperplane using least squares
        #The res is the residuals of the four points used to fit the hyperplane (summed distance of each of the
        #points to the hyperplane), it is a measure of gradient
        f, diff, rank, s = la.lstsq(points, neighbor.T, rcond=None)
        diff = diff.astype(int)

    # calculate the error
    error = np.ravel(image_array[1:-1,1:-1])-predict

    return image_array, predict, diff, error, A
```

%% Cell type:code id: tags:

``` python
"""
this huffman encoding code is found online
https://favtutor.com/blogs/huffman-coding
"""

class NodeTree(object):
    def __init__(self, left=None, right=None):
        self.left = left
        self.right = right

    def children(self):
        return self.left, self.right

    def __str__(self):
        return self.left, self.right


def huffman_code_tree(node, binString=''):
    '''
    Function to find Huffman Code
    '''
    if type(node) is str:
        return {node: binString}
    (l, r) = node.children()
    d = dict()
    d.update(huffman_code_tree(l, binString + '0'))
    d.update(huffman_code_tree(r, binString + '1'))
    return d


def make_tree(nodes):
    '''
    Function to make tree
    :param nodes: Nodes
    :return: Root of the tree
    '''
    while len(nodes) > 1:
        (key1, c1) = nodes[-1]
        (key2, c2) = nodes[-2]
        nodes = nodes[:-2]
        node = NodeTree(key1, key2)
        nodes.append((node, c1 + c2))
        #reverse True, decending order

        #There is a huge memory leak here, no idea how or why
        nodes = sorted(nodes, key=lambda x: x[1], reverse=True)
    return nodes[0][0]

def decode_string(huffman_string,node):
    if type(node) is str:
        return huffman_string, int(node)
    else:
        if (huffman_string[0] == '0'):
            return decode_string(huffman_string[1:],node.left)
        else:
            return decode_string(huffman_string[1:],node.right)


```

%% Cell type:code id: tags:

``` python
def huffman(tiff_image_path, num_bins=4, difference = True):
    """
    This function is used to encode the error based on the difference
    and split the difference into different bins

    Input:
    tiff_image_path     (string): path to the tiff file
    num_bins            (int): number of bins

    Return:
    huffman_encoding_list  list    (num_bins + 1): a list of dictionary
    image_array            ndarray (512, 640): original image
    new_error              ndarray (512, 640): error that includes the boundary
    diff                   ndarray (510, 638): difference of min and max of the 4 neighbors
    boundary               ndarray (2300,): the boundary values after subtracting the very first pixel value
    predict                ndarray (325380,): the list of predicted values
    bins                   list    (num_bins - 1,): a list of threshold to cut the bins
    A                      ndarray (3 X 3): system of equation

    """
    # get the image_array, etc
    image_array, predict, diff, error, A = predict_pix(tiff_image_path, difference)

    # calculate the number of points that will go in each bin
    data_points_per_bin = diff.size // num_bins

    # sort the difference and create the bins
    sorted_diff = np.sort(diff.copy())
    bins = [sorted_diff[i*data_points_per_bin] for i in range(1,num_bins)]

    # get the boundary
    boundary = np.hstack((image_array[0,:],image_array[-1,:],image_array[1:-1,0],image_array[1:-1,-1]))

    # take the difference of the boundary with the very first pixel
    boundary = boundary - image_array[0,0]

    #boundary is 1dim, so boundary[0] is just the first element
    boundary[0] = image_array[0,0]

    # huffman encode the boundary
    bound_vals_as_string = [str(i) for i in boundary]
    freq = dict(Counter(bound_vals_as_string))
    freq = sorted(freq.items(), key=lambda x: x[1], reverse=True)
    node1 = make_tree(freq)
    huffman_encoding_dict = huffman_code_tree(node1)
    nodelist = [node1]
    # create a list of huffman table
    huffman_encoding_list = [huffman_encoding_dict]
    n = len(bins)

    # loop through different bins
    for i in range (0,n):
        # the first bin
        if i == 0 :
            # get the point within the bin and huffman huffman_encoding_dict
            mask = diff <= bins[i]
            line_as_string = [str(i) for i in error[mask].astype(int)]
            freq = dict(Counter(line_as_string))
            freq = sorted(freq.items(), key=lambda x: x[1], reverse=True)
            node2 = make_tree(freq)
            huffman_encoding_dict = huffman_code_tree(node2)
            huffman_encoding_list.append(huffman_encoding_dict)
            nodelist.append(node2)
        # the middle bins
        else:
            # get the point within the bin and huffman huffman_encoding_dict
            mask = diff > bins[i-1]
            new_error = error[mask]
            mask2 = diff[mask] <= bins[i]
            line_as_string = [str(i) for i in new_error[mask2].astype(int)]
            freq = dict(Counter(line_as_string))
            freq = sorted(freq.items(), key=lambda x: x[1], reverse=True)
            node3 = make_tree(freq)
            huffman_encoding_dict = huffman_code_tree(node3)
            huffman_encoding_list.append(huffman_encoding_dict)
            nodelist.append(node3)
    # the last bin
    # get the point within the bin and huffman huffman_encoding_dict
    mask = diff > bins[-1]
    line_as_string = [str(i) for i in error[mask].astype(int)]
    freq = dict(Counter(line_as_string))
    freq = sorted(freq.items(), key=lambda x: x[1], reverse=True)
    node4 = make_tree(freq)
    huffman_encoding_dict = huffman_code_tree(node4)
    huffman_encoding_list.append(huffman_encoding_dict)
    nodelist.append(node4)
    # create a error matrix that includes the boundary (used in encoding matrix)
    new_error = np.copy(image_array)
    new_error[1:-1,1:-1] = np.reshape(error,(510, 638))
    keep = new_error[0,0]
    new_error[0,:] = new_error[0,:] - keep
    new_error[-1,:] = new_error[-1,:] - keep
    new_error[1:-1,0] = new_error[1:-1,0] - keep
    new_error[1:-1,-1] = new_error[1:-1,-1] - keep
    new_error[0,0] = keep
    # huffman_encoding_list = list(set(huffman_encoding_list))
    diff = np.reshape(diff,(510,638))
    # return the huffman dictionary
    return huffman_encoding_list, image_array, new_error, diff, boundary, predict, bins, A, nodelist
```

%% Cell type:code id: tags:

``` python
def encoder(error, list_dic, diff, bound, bins):
    """
    This function encode the matrix with huffman coding tables

    Input:
    error     (512, 640): a matrix with all the errors
    list_dic  (num_dic + 1,): a list of huffman coding table
    bound     (2300,): the boundary values after subtracting the very first pixel value
    bins       (num_bins - 1,): a list of threshold to cut the bins

    Return:
    encoded   (512, 640): encoded matrix
    """
    returnable_encode = ""
    # copy the error matrix (including the boundary)
    encoded = np.copy(error).astype(int).astype(str).astype(object)
    #diff = np.reshape(diff,(510,638))
    # loop through all the pixel to encode
    for i in range(encoded.shape[0]):
        for j in range(encoded.shape[1]):
            if i == 0 or i == encoded.shape[0]-1 or j == 0 or j == encoded.shape[1]-1:
                returnable_encode += list_dic[0][encoded[i][j]]
            elif diff[i-1][j-1] <= bins[0]:
                returnable_encode += list_dic[1][encoded[i][j]]
            elif diff[i-1][j-1] <= bins[1] and diff[i-1][j-1] > bins[0]:
                returnable_encode +=list_dic[2][encoded[i][j]]
            elif diff[i-1][j-1] <= bins[2] and diff[i-1][j-1] > bins[1]:
                returnable_encode +=list_dic[3][encoded[i][j]]
            else:
                returnable_encode += list_dic[4][encoded[i][j]]

    return returnable_encode
```

%% Cell type:code id: tags:

``` python
def decoder(A, encoded_string, list_dic, bins, use_diff, nodes):
    """
    This function decodes the encoded_matrix.
    Input:
    A               (3 X 3): system of equation
    list_dic        (num_dic + 1,): a list of huffman coding table
    encoded_matrix  (512, 640): encoded matrix
    bins            (num_bins - 1,): a list of threshold to cut the bins

    Return:
    decode_matrix   (512, 640): decoded matrix
    """
    # change the dictionary back to list
    # !!!!!WARNING!!!! has to change this part, everytime you change the number of bins
    the_keys0 = list(list_dic[0].keys())
    the_values0 = list(list_dic[0].values())

    the_keys1 = list(list_dic[1].keys())
    the_values1 = list(list_dic[1].values())

    the_keys2 = list(list_dic[2].keys())
    the_values2 = list(list_dic[2].values())

    the_keys3 = list(list_dic[3].keys())
    the_values3 = list(list_dic[3].values())

    the_keys4 = list(list_dic[4].keys())
    the_values4 = list(list_dic[4].values())

    #Matrix system of points that will be used to solve the least squares fitting hyperplane
    points = np.array([[-1,-1,1], [-1,0,1], [-1,1,1], [0,-1,1]])

    decode_matrix = np.zeros((512,640))
    # loop through all the element in the matrix
    for i in range(decode_matrix.shape[0]):
        for j in range(decode_matrix.shape[1]):
            # if it's the very first pixel on the image
            if i == 0 and j == 0:
                encoded_string, colorvalue = decode_string(encoded_string, nodes[0])
                decode_matrix[i][j] = colorvalue

            # if it's on the boundary (any of the 4 edges)
            elif i == 0 or i == decode_matrix.shape[0]-1 or j == 0 or j == decode_matrix.shape[1]-1:
                encoded_string, colorvalue = decode_string(encoded_string, nodes[0])
                decode_matrix[i][j] = colorvalue + decode_matrix[0][0]
            # if not the boundary
            else:
                # predict the image with the known pixel value
                z0 = decode_matrix[i-1][j-1]
                z1 = decode_matrix[i-1][j]
                z2 = decode_matrix[i-1][j+1]
                z3 = decode_matrix[i][j-1]
                y0 = int(-z0+z2-z3)
                y1 = int(z0+z1+z2)
                y2 = int(-z0-z1-z2-z3)
                y = np.vstack((y0,y1,y2))
                if use_diff:
                    difference = max(z0,z1,z2,z3) - min(z0,z1,z2,z3)
                else:

                    f, difference, rank, s = la.lstsq(points, [z0,z1,z2,z3], rcond=None)
                    difference = difference.astype(int)

                predict = np.round(np.round(np.linalg.solve(A,y)[-1][0],1))

                # add on the difference by searching the dictionary
                # !!!!!WARNING!!!! has to change this part, eveytime you change the number of bins
                if difference <= bins[0]:
                    encoded_string, colorvalue = decode_string(encoded_string, nodes[1])
                    decode_matrix[i][j] = colorvalue + int(predict)
                elif difference <= bins[1] and difference > bins[0]:
                    encoded_string, colorvalue = decode_string(encoded_string, nodes[2])
                    decode_matrix[i][j] = colorvalue + int(predict)
                elif difference <= bins[2] and difference > bins[1]:
                    encoded_string, colorvalue = decode_string(encoded_string, nodes[3])
                    decode_matrix[i][j] = colorvalue + int(predict)
                else:
                    encoded_string, colorvalue = decode_string(encoded_string, nodes[4])
                    decode_matrix[i][j] = colorvalue + int(predict)

    return decode_matrix.astype(int)
```

%% Cell type:code id: tags:

``` python
def compress_rate(image_array, new_error, diff, bound, huffman_encoding_list, bins):
    '''
    This function is used to calculate the compression rate.
    Input:
    image_array      (512, 640): original_core image
    new_error  (512, 640): error that includes the boundary
    diff       (510, 638): difference of min and max of the 4 neighbors
    bound      (2300,): the boundary values after subtracting the very first pixel value
    huffman_encoding_list   (num_dic + 1,): a list of huffman coding table
    bins       (num_bins - 1,): a list of threshold to cut the bins

    Return:
    compression rate
    '''
    # the bits for the original image
    o_len = 0
    # the bits for the compressed image
    c_len = 0
    # initializing the varible

    #this was unused
    # im = np.reshape(image,(512, 640))

    real_boundary = np.hstack((image_array[0,:],image_array[-1,:],image_array[1:-1,0],image_array[1:-1,-1]))
    #Bryce's notes: Why are they all reshaped?
    original_core = image_array[1:-1,1:-1].reshape(-1)
    diff = diff.reshape(-1)
    error = new_error[1:-1,1:-1].reshape(-1)

    # calculate the bit for boundary
    for i in range(0,len(bound)):
        o_len += len(bin(real_boundary[i])[2:])
        c_len += len(huffman_encoding_list[0][str(bound[i])])

    # calculate the bit for the pixels inside the boundary
    for i in range(0,len(original_core)):

        # for the original image
        o_len += len(bin(original_core[i])[2:])

        # check the difference and find the coresponding huffman table
        # !!!!!WARNING!!!! has to change this part, eveytime you change the number of bins
        if diff[i] <= bins[0]:
            c_len += len(huffman_encoding_list[1][str(int(error[i]))])

        elif diff[i] <= bins[1] and diff[i] > bins[0]:
            c_len += len(huffman_encoding_list[2][str(int(error[i]))])

        elif diff[i] <= bins[2] and diff[i] > bins[1]:
            c_len += len(huffman_encoding_list[3][str(int(error[i]))])

        else:
            c_len += len(huffman_encoding_list[4][str(int(error[i]))])

    return c_len/o_len
```

%% Cell type:code id: tags:

``` python
scenes = file_extractor()
images = image_extractor(scenes)
list_dic, image, new_error, diff, bound, predict, bins, A, nodes = huffman(images[0], 4, False)
encoded_string = encoder(new_error, list_dic, diff, bound, bins)
reconstruct_image = decoder(A, encoded_string, list_dic, bins, False,nodes)
print(np.allclose(image, reconstruct_image))
print(len(list_dic))
```

%% Output

    True
    5

%% Cell type:code id: tags:

``` python
```