From 2e53b8fbc7b833a48e645fed38d5efb8b3602a17 Mon Sep 17 00:00:00 2001
From: tgupta6 <tgupta6@illinois.edu>
Date: Mon, 7 Mar 2016 22:50:24 -0600
Subject: [PATCH] Second model of obj classifier and initial code for attribute
 classifier

---
 color_classifiers/__init__.py               |   0
 color_classifiers/atr_data_io_helper.py     |  90 +++++++++
 color_classifiers/eval_atr_classifier.py    |  69 +++++++
 color_classifiers/train_atr_classifier.py   | 197 ++++++++++++++++++++
 object_classifiers/eval_obj_classifier.py   |  51 ++++-
 object_classifiers/obj_data_io_helper.py    |  45 +++--
 object_classifiers/obj_data_io_helper.pyc   | Bin 3016 -> 4545 bytes
 object_classifiers/train_obj_classifier.py  |  68 +++++--
 object_classifiers/train_obj_classifier.pyc | Bin 6532 -> 7697 bytes
 9 files changed, 482 insertions(+), 38 deletions(-)
 create mode 100644 color_classifiers/__init__.py
 create mode 100644 color_classifiers/atr_data_io_helper.py
 create mode 100644 color_classifiers/eval_atr_classifier.py
 create mode 100644 color_classifiers/train_atr_classifier.py

diff --git a/color_classifiers/__init__.py b/color_classifiers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/color_classifiers/atr_data_io_helper.py b/color_classifiers/atr_data_io_helper.py
new file mode 100644
index 0000000..67337b8
--- /dev/null
+++ b/color_classifiers/atr_data_io_helper.py
@@ -0,0 +1,90 @@
+import json
+import sys
+import os
+import matplotlib.pyplot as plt
+import matplotlib.image as mpimg
+import numpy as np
+import tensorflow as tf
+from scipy import misc
+
+def atr_mini_batch_loader(json_filename, image_dir, mean_image, start_index, batch_size, img_height=100, img_width=100, channels=3):
+
+    with open(json_filename, 'r') as json_file: 
+        json_data = json.load(json_file)
+
+    atr_images = np.empty(shape=[9*batch_size, img_height/3, img_width/3, channels])
+    atr_labels = np.zeros(shape=[9*batch_size, 4])
+
+    for i in range(start_index, start_index + batch_size):
+        image_name = os.path.join(image_dir, str(i) + '.jpg')
+        image = misc.imresize(mpimg.imread(image_name),(img_height, img_width), interp='nearest')
+#        image.resize((img_height, img_width, 3))
+        crop_shape = np.array([image.shape[0], image.shape[1]])/3
+        selected_anno = [q for q in json_data if q['image_id']==i]
+        grid_config = selected_anno[0]['config']
+        
+        counter = 0;
+        for grid_row in range(0,3):
+            for grid_col in range(0,3):
+                start_row = grid_row*crop_shape[0]
+                start_col = grid_col*crop_shape[1]
+#                print([start_row, start_col])
+                cropped_image = image[start_row:start_row+crop_shape[0], start_col:start_col+crop_shape[1], :]
+                if np.ndim(mean_image)==0:
+                    atr_images[9*(i-start_index)+counter,:,:,:] = cropped_image/254.
+                else:
+                    atr_images[9*(i-start_index)+counter,:,:,:] = (cropped_image-mean_image)/254
+                atr_labels[9*(i-start_index)+counter, grid_config[6*grid_row+2*grid_col+1]] = 1
+                counter = counter + 1
+
+    # imgplot = plt.imshow(obj_images[0,:,:,:].astype(np.uint8))
+    # plt.show()
+    return (atr_images, atr_labels)
+
+def mean_image_batch(json_filename, image_dir, start_index, batch_size, img_height=100, img_width=100, channels=3):
+    batch = obj_mini_batch_loader(json_filename, image_dir, np.empty([]), start_index, batch_size, img_height, img_width, channels)
+    mean_image = np.mean(batch[0], 0)
+    return mean_image
+
+def mean_image(json_filename, image_dir, num_images, batch_size, img_height=100, img_width=100, channels=3):
+    max_iter = np.floor(num_images/batch_size)
+    mean_image = np.zeros([img_height/3, img_width/3, channels])
+    for i in range(max_iter.astype(np.int16)):
+        mean_image = mean_image + mean_image_batch(json_filename, image_dir, 1+i*batch_size, batch_size, img_height, img_width, channels)
+
+    mean_image = mean_image/max_iter
+    tmp_mean_image = mean_image*254
+    # imgplot = plt.imshow(tmp_mean_image.astype(np.uint8))
+    # plt.show()
+    return mean_image
+
+
+class html_atr_table_writer():
+    def __init__(self, filename):
+        self.filename = filename
+        self.html_file = open(self.filename, 'w')
+        self.html_file.write("""<!DOCTYPE html>\n<html>\n<body>\n<table border="1" style="width:100%"> \n""")
+    
+    def add_element(self, col_dict):
+        self.html_file.write('    <tr>\n')
+        for key in range(len(col_dict)):
+            self.html_file.write("""    <td>{}</td>\n""".format(col_dict[key]))
+        self.html_file.write('    </tr>\n')
+
+    def image_tag(self, image_path, height, width):
+        return """<img src="{}" alt="IMAGE NOT FOUND!" height={} width={}>""".format(image_path,height,width)
+        
+    def close_file(self):
+        self.html_file.write('</table>\n</body>\n</html>')
+        self.html_file.close()
+
+        
+    
+
+if __name__=="__main__":
+
+    html_writer = html_atr_table_writer('/home/tanmay/Code/GenVQA/Exp_Results/Atr_Classifier_v_1/trial.html')
+    col_dict={0: 'sam', 1: html_writer.image_tag('something.png',25,25)}
+    html_writer.add_element(col_dict)
+    html_writer.close_file()
+
diff --git a/color_classifiers/eval_atr_classifier.py b/color_classifiers/eval_atr_classifier.py
new file mode 100644
index 0000000..2f41202
--- /dev/null
+++ b/color_classifiers/eval_atr_classifier.py
@@ -0,0 +1,69 @@
+import sys
+import os
+import matplotlib.pyplot as plt
+import matplotlib.image as mpimg
+import numpy as np
+from scipy import misc
+import tensorflow as tf
+import obj_data_io_helper as shape_data_loader 
+from train_obj_classifier import placeholder_inputs, comp_graph_v_1, evaluation
+
+sess=tf.InteractiveSession()
+
+x, y, keep_prob = placeholder_inputs()
+y_pred = comp_graph_v_1(x, y, keep_prob)
+
+accuracy = evaluation(y, y_pred)
+
+saver = tf.train.Saver()
+
+saver.restore(sess, '/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier_v_1/obj_classifier_9.ckpt')
+
+mean_image = np.load('/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier_v_1/mean_image.npy')
+
+# Test Data
+test_json_filename = '/home/tanmay/Code/GenVQA/GenVQA/shapes_dataset/test_anno.json'
+image_dir = '/home/tanmay/Code/GenVQA/GenVQA/shapes_dataset/images'
+
+# Base dir for html visualizer
+html_dir = '/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier_v_1/html'
+if not os.path.exists(html_dir):
+    os.mkdir(html_dir)
+
+# HTML file writer
+html_writer = shape_data_loader.html_obj_table_writer(os.path.join(html_dir,'index.html'))
+col_dict={
+    0: 'Grount Truth',
+    1: 'Prediction',
+    2: 'Image'}
+html_writer.add_element(col_dict)
+
+shape_dict = {
+    0: 'blank',
+    1: 'rectangle',
+    2: 'triangle',
+    3: 'circle'}
+
+batch_size = 100
+correct = 0
+for i in range(50): 
+    test_batch = shape_data_loader.obj_mini_batch_loader(test_json_filename, image_dir, mean_image, 10000+i*batch_size, batch_size, 75, 75)
+    feed_dict_test={x: test_batch[0], y: test_batch[1], keep_prob: 1.0}
+    result = sess.run([accuracy, y_pred], feed_dict=feed_dict_test)
+    correct = correct + result[0]*batch_size
+    print(correct)
+
+    for row in range(batch_size*9):
+        gt_id = np.argmax(test_batch[1][row,:])
+        pred_id = np.argmax(result[1][row, :])
+        if not gt_id==pred_id:
+            img_filename = os.path.join(html_dir,'{}_{}.png'.format(i,row))
+            misc.imsave(img_filename, test_batch[0][row,:,:,:])
+            col_dict = {
+                0: shape_dict[gt_id],
+                1: shape_dict[pred_id],
+                2: html_writer.image_tag('{}_{}.png'.format(i,row), 25, 25)}
+            html_writer.add_element(col_dict)
+
+html_writer.close_file()
+print('Test Accuracy: {}'.format(correct/5000))
diff --git a/color_classifiers/train_atr_classifier.py b/color_classifiers/train_atr_classifier.py
new file mode 100644
index 0000000..6d9c88f
--- /dev/null
+++ b/color_classifiers/train_atr_classifier.py
@@ -0,0 +1,197 @@
+import sys
+import os
+import matplotlib.pyplot as plt
+import matplotlib.image as mpimg
+import numpy as np
+import tensorflow as tf
+import atr_data_io_helper as atr_data_loader
+
+def plot_accuracy(xdata, ydata, xlim=None, ylim=None, savePath=None):
+    fig, ax = plt.subplots( nrows=1, ncols=1 )
+    ax.plot(xdata, ydata)
+    plt.xlabel('Iterations')
+    plt.ylabel('Accuracy')
+
+    if not xlim==None:
+        plt.xlim(xlim)
+
+    if not ylim==None:
+        plt.ylim(ylim)
+
+    if not savePath==None:
+        fig.savefig(savePath)
+
+
+    plt.close(fig)
+
+def plot_accuracies(xdata, ydata_train, ydata_val, xlim=None, ylim=None, savePath=None):
+    fig, ax = plt.subplots( nrows=1, ncols=1 )
+    ax.plot(xdata, ydata_train, xdata, ydata_val)
+    plt.xlabel('Epochs')
+    plt.ylabel('Accuracy')
+    plt.legend(['Train', 'Val'], loc='lower right')
+    if not xlim==None:
+        plt.xlim(xlim)
+
+    if not ylim==None:
+        plt.ylim(ylim)
+
+    if not savePath==None:
+        fig.savefig(savePath)
+
+
+    plt.close(fig)
+
+
+def weight_variable(shape):
+    initial = tf.truncated_normal(shape, stddev=0.1)
+    return tf.Variable(initial)
+
+def bias_variable(shape):
+    initial = tf.constant(0.1, shape=shape)
+    return tf.Variable(initial)
+
+def conv2d(x, W):
+    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
+
+def max_pool_2x2(x):
+    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
+
+def max_pool_4x4(x):
+    return tf.nn.max_pool(x, ksize=[1, 4, 4, 1], strides=[1, 4, 4, 1], padding='SAME')
+
+def placeholder_inputs():
+    # Specify placeholder_inputs
+    x = tf.placeholder(tf.float32, shape=[None, 25, 25, 3])
+    y = tf.placeholder(tf.float32, shape=[None, 4])
+    keep_prob = tf.placeholder(tf.float32)
+    return x, y, keep_prob
+
+def comp_graph_v_1(x, y, keep_prob):
+    # Specify computation graph
+    W_conv1 = weight_variable([5, 5, 3, 10])
+    b_conv1 = bias_variable([10])
+    
+    h_conv1 = tf.nn.relu(conv2d(x, W_conv1) + b_conv1)
+    
+    h_pool1 = max_pool_2x2(h_conv1)
+    #print(tf.Tensor.get_shape(h_pool1))
+    
+    W_fc1 = weight_variable([13*13*10, 4])
+    b_fc1 = bias_variable([4])
+    
+    h_pool1_flat = tf.reshape(h_pool1, [-1, 13*13*10])
+    h_pool1_flat_drop = tf.nn.dropout(h_pool1_flat, keep_prob)
+    
+    y_pred = tf.nn.softmax(tf.matmul(h_pool1_flat_drop,W_fc1) + b_fc1)
+    
+    return y_pred
+
+def comp_graph_v_2(x, y, keep_prob):
+    # Specify computation graph
+    W_conv1 = weight_variable([5, 5, 3, 10])
+    b_conv1 = bias_variable([10])
+    
+    h_conv1 = tf.nn.relu(conv2d(x, W_conv1) + b_conv1)
+    h_pool1 = max_pool_2x2(h_conv1)
+    h_conv1_drop = tf.nn.dropout(h_pool1, keep_prob)
+    
+    W_conv2 = weight_variable([5, 5, 10, 20])
+    b_conv2 = bias_variable([20])
+    
+    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
+    h_pool2 = max_pool_2x2(h_conv2)
+    h_conv2_drop = tf.nn.dropout(h_pool2, keep_prob)
+
+    W_fc1 = weight_variable([7*7*20, 4])
+    b_fc1 = bias_variable([4])
+    
+    h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*20])
+    h_pool2_flat_drop = tf.nn.dropout(h_pool2_flat, keep_prob)
+    
+    y_pred = tf.nn.softmax(tf.matmul(h_pool2_flat_drop,W_fc1) + b_fc1)
+    
+    return y_pred
+
+def evaluation(y, y_pred):
+    # Evaluation function
+    correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(y_pred,1))
+    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
+    #tf.scalar_summary("accuracy", accuracy)
+
+    return accuracy
+
+
+def train():
+    # Start session
+    sess = tf.InteractiveSession()
+
+    x, y, keep_prob = placeholder_inputs()
+    y_pred = comp_graph_v_2(x, y, keep_prob)
+
+    # Specify loss
+    cross_entropy = -tf.reduce_sum(y*tf.log(y_pred))
+    
+    # Specify training method
+    train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
+
+    # Evaluator
+    accuracy = evaluation(y, y_pred)
+    
+    # Merge summaries and write them to ~/Code/Tensorflow_Exp/logDir
+    merged = tf.merge_all_summaries()
+
+    # Output dir
+    outdir = '/home/tanmay/Code/GenVQA/Exp_Results/Atr_Classifier_v_1/'
+    if not os.path.exists(outdir):
+        os.mkdir(outdir)
+
+    # Training Data
+    img_width = 75
+    img_height = 75
+    train_json_filename = '/home/tanmay/Code/GenVQA/GenVQA/shapes_dataset/train_anno.json'
+    image_dir = '/home/tanmay/Code/GenVQA/GenVQA/shapes_dataset/images'
+    mean_image = atr_data_loader.mean_image(train_json_filename, image_dir, 1000, 100, img_height, img_width)
+    np.save(os.path.join(outdir, 'mean_image.npy'), mean_image)
+
+    # Val Data
+    val_batch = atr_data_loader.atr_mini_batch_loader(train_json_filename, image_dir, mean_image, 9501, 499, img_height, img_width)
+    feed_dict_val={x: val_batch[0], y: val_batch[1], keep_prob: 1.0}
+    
+    # Session Saver
+    saver = tf.train.Saver()
+
+    # Start Training
+    sess.run(tf.initialize_all_variables())
+    batch_size = 100
+    max_epoch = 10
+    max_iter = 95
+    val_acc_array_iter = np.empty([max_iter*max_epoch])
+    val_acc_array_epoch = np.zeros([max_epoch])
+    train_acc_array_epoch = np.zeros([max_epoch])
+    for epoch in range(max_epoch):
+        for i in range(max_iter):
+            train_batch = atr_data_loader.atr_mini_batch_loader(train_json_filename, image_dir, mean_image, 1+i*batch_size, batch_size, img_height, img_width)
+            feed_dict_train={x: train_batch[0], y: train_batch[1], keep_prob: 0.5}
+
+            _, current_train_batch_acc = sess.run([train_step, accuracy], feed_dict=feed_dict_train)
+
+            train_acc_array_epoch[epoch] =  train_acc_array_epoch[epoch] + current_train_batch_acc
+            val_acc_array_iter[i+epoch*max_iter] = accuracy.eval(feed_dict_val)
+            print('Step: {}  Val Accuracy: {}'.format(i+1+epoch*max_iter, val_acc_array_iter[i+epoch*max_iter]))
+            plot_accuracy(np.arange(0,i+1+epoch*max_iter)+1, val_acc_array_iter[0:i+1+epoch*max_iter], xlim=[1, max_epoch*max_iter], ylim=[0, 1.], savePath=os.path.join(outdir,'valAcc_vs_iter.pdf'))
+            
+            
+        train_acc_array_epoch[epoch] = train_acc_array_epoch[epoch] / max_iter 
+        val_acc_array_epoch[epoch] = val_acc_array_iter[i+epoch*max_iter]
+        
+        plot_accuracies(xdata=np.arange(0,epoch+1)+1, ydata_train=train_acc_array_epoch[0:epoch+1], ydata_val=val_acc_array_epoch[0:epoch+1], xlim=[1, max_epoch], ylim=[0, 1.], savePath=os.path.join(outdir,'acc_vs_epoch.pdf'))
+
+        save_path = saver.save(sess, os.path.join(outdir,'obj_classifier_{}.ckpt'.format(epoch)))
+            
+    
+    sess.close()
+
+if __name__=='__main__':
+    train()
+
diff --git a/object_classifiers/eval_obj_classifier.py b/object_classifiers/eval_obj_classifier.py
index d9dd4e1..9853116 100644
--- a/object_classifiers/eval_obj_classifier.py
+++ b/object_classifiers/eval_obj_classifier.py
@@ -3,34 +3,67 @@ import os
 import matplotlib.pyplot as plt
 import matplotlib.image as mpimg
 import numpy as np
+from scipy import misc
 import tensorflow as tf
-import obj_data_io_helper as shape_data_loader
-from train_obj_classifier import placeholder_inputs, comp_graph, evaluation
+import obj_data_io_helper as shape_data_loader 
+from train_obj_classifier import placeholder_inputs, comp_graph_v_1, comp_graph_v_2, evaluation
 
 sess=tf.InteractiveSession()
 
 x, y, keep_prob = placeholder_inputs()
-y_pred = comp_graph(x, y, keep_prob)
+y_pred = comp_graph_v_2(x, y, keep_prob)
 
 accuracy = evaluation(y, y_pred)
 
 saver = tf.train.Saver()
 
-saver.restore(sess, '/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier/obj_classifier_5.ckpt')
+saver.restore(sess, '/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier_v_2/obj_classifier_1.ckpt')
 
-mean_image = np.load('/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier/mean_image.npy')
+mean_image = np.load('/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier_v_2/mean_image.npy')
 
 # Test Data
 test_json_filename = '/home/tanmay/Code/GenVQA/GenVQA/shapes_dataset/test_anno.json'
 image_dir = '/home/tanmay/Code/GenVQA/GenVQA/shapes_dataset/images'
 
+# Base dir for html visualizer
+html_dir = '/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier_v_2/html'
+if not os.path.exists(html_dir):
+    os.mkdir(html_dir)
+
+# HTML file writer
+html_writer = shape_data_loader.html_obj_table_writer(os.path.join(html_dir,'index.html'))
+col_dict={
+    0: 'Grount Truth',
+    1: 'Prediction',
+    2: 'Image'}
+html_writer.add_element(col_dict)
+
+shape_dict = {
+    0: 'blank',
+    1: 'rectangle',
+    2: 'triangle',
+    3: 'circle'}
+
 batch_size = 100
 correct = 0
-for i in range(50):
-    test_batch = shape_data_loader.obj_mini_batch_loader(test_json_filename, image_dir, mean_image, 10000+i*batch_size, batch_size)
-
+for i in range(50): 
+    test_batch = shape_data_loader.obj_mini_batch_loader(test_json_filename, image_dir, mean_image, 10000+i*batch_size, batch_size, 75, 75)
     feed_dict_test={x: test_batch[0], y: test_batch[1], keep_prob: 1.0}
-    correct = correct + accuracy.eval(feed_dict_test)*batch_size
+    result = sess.run([accuracy, y_pred], feed_dict=feed_dict_test)
+    correct = correct + result[0]*batch_size
     print(correct)
 
+    for row in range(batch_size*9):
+        gt_id = np.argmax(test_batch[1][row,:])
+        pred_id = np.argmax(result[1][row, :])
+        if not gt_id==pred_id:
+            img_filename = os.path.join(html_dir,'{}_{}.png'.format(i,row))
+            misc.imsave(img_filename, test_batch[0][row,:,:,:])
+            col_dict = {
+                0: shape_dict[gt_id],
+                1: shape_dict[pred_id],
+                2: html_writer.image_tag('{}_{}.png'.format(i,row), 25, 25)}
+            html_writer.add_element(col_dict)
+
+html_writer.close_file()
 print('Test Accuracy: {}'.format(correct/5000))
diff --git a/object_classifiers/obj_data_io_helper.py b/object_classifiers/obj_data_io_helper.py
index 3f5484a..d0427ab 100644
--- a/object_classifiers/obj_data_io_helper.py
+++ b/object_classifiers/obj_data_io_helper.py
@@ -5,18 +5,20 @@ import matplotlib.pyplot as plt
 import matplotlib.image as mpimg
 import numpy as np
 import tensorflow as tf
+from scipy import misc
 
 def obj_mini_batch_loader(json_filename, image_dir, mean_image, start_index, batch_size, img_height=100, img_width=100, channels=3):
 
     with open(json_filename, 'r') as json_file: 
         json_data = json.load(json_file)
 
-    obj_images = np.empty(shape=[9*batch_size, img_height, img_width, channels])
+    obj_images = np.empty(shape=[9*batch_size, img_height/3, img_width/3, channels])
     obj_labels = np.zeros(shape=[9*batch_size, 4])
 
     for i in range(start_index, start_index + batch_size):
         image_name = os.path.join(image_dir, str(i) + '.jpg')
-        image = mpimg.imread(image_name)
+        image = misc.imresize(mpimg.imread(image_name),(img_height, img_width), interp='nearest')
+#        image.resize((img_height, img_width, 3))
         crop_shape = np.array([image.shape[0], image.shape[1]])/3
         selected_anno = [q for q in json_data if q['image_id']==i]
         grid_config = selected_anno[0]['config']
@@ -46,26 +48,43 @@ def mean_image_batch(json_filename, image_dir, start_index, batch_size, img_heig
 
 def mean_image(json_filename, image_dir, num_images, batch_size, img_height=100, img_width=100, channels=3):
     max_iter = np.floor(num_images/batch_size)
-    mean_image = np.zeros([img_height, img_width, channels])
+    mean_image = np.zeros([img_height/3, img_width/3, channels])
     for i in range(max_iter.astype(np.int16)):
         mean_image = mean_image + mean_image_batch(json_filename, image_dir, 1+i*batch_size, batch_size, img_height, img_width, channels)
 
     mean_image = mean_image/max_iter
     tmp_mean_image = mean_image*254
-    imgplot = plt.imshow(tmp_mean_image.astype(np.uint8))
-    plt.show()
+    # imgplot = plt.imshow(tmp_mean_image.astype(np.uint8))
+    # plt.show()
     return mean_image
 
 
-if __name__=="__main__":
+class html_obj_table_writer():
+    def __init__(self, filename):
+        self.filename = filename
+        self.html_file = open(self.filename, 'w')
+        self.html_file.write("""<!DOCTYPE html>\n<html>\n<body>\n<table border="1" style="width:100%"> \n""")
+    
+    def add_element(self, col_dict):
+        self.html_file.write('    <tr>\n')
+        for key in range(len(col_dict)):
+            self.html_file.write("""    <td>{}</td>\n""".format(col_dict[key]))
+        self.html_file.write('    </tr>\n')
+
+    def image_tag(self, image_path, height, width):
+        return """<img src="{}" alt="IMAGE NOT FOUND!" height={} width={}>""".format(image_path,height,width)
+        
+    def close_file(self):
+        self.html_file.write('</table>\n</body>\n</html>')
+        self.html_file.close()
 
-    json_filename = '/home/tanmay/Code/GenVQA/GenVQA/shapes_dataset/train_anno.json'
-    batch_size = 1
-    start_index = 0
+        
+    
 
-    image_dir = '/home/tanmay/Code/GenVQA/GenVQA/shapes_dataset/images'
+if __name__=="__main__":
 
-    batch = obj_mini_batch_loader(json_filename, image_dir, start_index, batch_size)
-    print(batch[0].shape)
-    print(batch[1].shape)
+    html_writer = html_obj_table_writer('/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier_v_1/trial.html')
+    col_dict={0: 'sam', 1: html_writer.image_tag('something.png',25,25)}
+    html_writer.add_element(col_dict)
+    html_writer.close_file()
 
diff --git a/object_classifiers/obj_data_io_helper.pyc b/object_classifiers/obj_data_io_helper.pyc
index 73a426be9496860d6a43819bf5c4c1506db53ffc..48169a55a825c93a57adb1cf4cc9cfeb258f4b15 100644
GIT binary patch
literal 4545
zcmcInTXQ2v6+S)kMUv&q+VVwV0j;-E5nCy1O^SsIve{+5goMOP#vw~ol}wFiM$#yA
zk!N~YJ3I40dEu2;ehzQ^iu{T^@Q|v&cTP(-1bCndOEW#EyU+AF=R23~seH9u|Ld1u
ze(cfoH;?B-+<8M0`M4BCMExsTQD4o*E$Uk{x<cbQ>d()*3p8G&es$KZQNKQ;m#E(m
z+M;Ng;uY$zkn$_U*Btrt^r}MrRq2|M1sPfUr;$Y&xkWw%psmp>qD!Jr`s);IkYA_I
zEy}7YSSNppE}?RhLTC!H%k&^<(s+~n74jiul`bJ<jUM#3h$prw*y1-7cXoJDl?VtY
zopqoR{vE&L4xk}Dhb9hlF9b>yR?v7=G%egqxF6%r|7d}csE{QtE&9_Il~vM;#ugP-
z7F5>g85a2Pw^|VNp=O1OIcgTba*j5O1@l;>;T+UaWu2H4lUJO8Y-q{c5}h|Jy+8$)
zkX5!J^XSvRsj@+Ok;YXT*QijW>r^yYy1zioQZAETqA{43X}m(kGS5K)2p>SqA_IMC
zsd7bRuTrr>WB8*+ttnNf7OQ{-p>nxOL)ct{<l9uPQ32K^ur|akUvvL6i_X5L@)i~1
zz7|~5D&egfl^b+^q+kPREGGKDrOGCq-}}~ROO=~cG^v0ap-Hpcq5=cxL7Qf|%~}>1
z!(}+O{{YZ2jLR_b0I<geVo-q!J7OTzGRD+I<8M^4!;!ODAcg^JgTdWt78|sE_4dCL
zgE{IYJ;JM4YzN`?zrPMciluLhEC3Tc(4;2976v!BTkug2+JJo<@SQzV&(0p<{yjZ|
z##jOKC!Je}$74huQKmV`^N~9V!Wt-v-EfY_xY-*|hQJ@=i4L*};)W;*+$_j-2$DjH
zeh&38c87rz`ois{$sioU@%a5EeHebj46+PzfFye)WztEI2r<r6Lqw_TYi3F&qFE46
z^o3Ag1X-F3mAT0<5GD+;mJ?Tx%rYtT<m}6JX5&HHs0Mw*F9LZvo`mt#q&N#8jO}-`
z%)PK#tSpoGVcg-|MG6qHZq6W#g2asjEg*@lewZP5koSR`IAT8XhDE!XcEZFD&Sm=4
z)!xVv`OFRJ&L{|nBRySe=)3@GBhl0wxk(a4xlCQjz3%z0c4g7@bZkWCSKY`x<u}<O
zp$r<2h|+AM(U4W}vUK8z%Z2+)tjvQb@O0oi&>$5<hFR!4#*^ZAFMZBIAd1RdmcF>|
zcxfbh8uNLIkup7=V<cJd1R_M04hBe?{Fk7*qcje>+D&5jqWd`YgYG*)^6`g{W)D~l
z{TwfH^E@1cL6&zJ1V+ROQ$QL`f^2Vcp*OJ@&%|L8ItF|Wsvu+2^Ec6uwXQa;d+N6J
zn)<$a!+JygK&@B{Y774rP(M<e>Zevuy{We5JdSv_gl{S1M<7fY=i({V8-dO>5*0-X
zttpn5RVo*S0fVTTidwk2z*f23pL96fjzcum;UKo}bHhs797Ym|99kWgY;SY3!_8~l
ze21Ic+<cdt@1c=E7JF?D-J61U4QpRVyaAX&%{jeE-2;Y#8QZ|d2xfl<0nElTkTwAq
zgM%S}hK*jKB4Qik#{vy23@2bZe~(Krz{7EZdFOc?*U_i<d9MVDyuV?4!Cr%PFj7GY
z{1+!Iq6}8)?0u9!z`lqLNzg~>ZesToKQQ>mrtV4daIxyJ+r=3I>mW+g%x>@)$FbBn
zH`f;vQ&UiM_8-`9@q29cwX-A-ljrd?SO25is(2%I&z%stC7!eIvWOLK;)!#O^$mGc
zWbQ*+&LT-|s5Y`<&#J3>MUrWx<H+HR((Y*#I4?5R)MH-$F=Ri-EtwVIbSNjnS0J#T
zDn5+$aj3F{fTJry%#(d0LGN)A@Lj<zq4Glz@F%?L001Gzvl9~!!p{;lV_6l!pnO6y
z@%v^+dyzi@-Me%2^zlcZ{Q60o)jzEFrjOIqzd%n+X`iMU>cYY9{%#x2jDmw*DaCK^
z-@pI*?qRzwDoTl)m4BP#LMoe}!ow0E0*Yj`<SmNn!0v(dhI}SEIoM=E?KnS!oN4ye
zP`LzBhJ%Z~K>8U71`QXSBN?R5e@xgxIlH7UNp+V>gRWt;IPKUK7PU;?sk6JtqpOu9
zO%42#LpfKfHhw*w9oBQM6sG4NzASrP?PHKjf(&+<r^7nh2izQTBgw_OW3!bc8>Ct6
zYVoekc8cNHP8>h<wCwbs2NyR5GUu}E`wn)-82TKcn{&33T2n1`M|o`Z>=vXqFaUso
zw80If2%i+wQlI|>vpwwp?L6}ic3+meZ8y>fyYGJR=$$9+<EJ0BfARD;$47T|+va?5
z@Um=6Jc2ls&kUOr&1@C|27_sKc{u@b1)d%)7*~1srm$gG8`0VwzK2fESxm~~dcZe#
z1(Q1<u4BeT-?YykD%1RB_T!v4I0OpU86;_Unyg*PT@wUXu_9^bMQI)wXc&;2GL~@`
zQF09Y06jSza~yOS^l3WsQOj|p<~vTD`p;1<9s3~|Y%WRmyWIQ=&9$CF{a?>N_S+D^
z$t0n=plb8Ag<55*VeE#9<K!I4|Mk4}<b2}TLH<0_dH00B3!KMSXD;V=&OQ=1bfZ1?
zi8*S!aftOO=j6?Ch|{Am8SYJzVaVqY`9d%TbJwn-G4EaEwrq0zF@hwYM5&I#Qyk%#
zlI)+7To5La)^hZ?W|Z)=U*|<R?<63w*G?|XzRgb4L6WB#cJCMRxu6HagwJ#^VS0Mz
z;*Hzvyo{awJ{mayONlqzo$(#VvvK83dr|z=Gck0?>6Ew8G!*|=@#p87wZ6MPzrMV&
M)vy}su3A?A0FP+FKL7v#

delta 1421
zcmZuwO>f*(6g|(L@p!(!JTsY)Dot9@5UNB~Na+G9BqD)?)P*AvX{ut}*qPX5Jd^RO
znn<>05h^xpkbgk8>=9d}?)VRp+Adfiv4jN@5^(REkw6u;-+k}hcYJ;CxyR3M{M1|1
zf2)>WeslHN5tdh;<_~Gazu?=ohjR@cJvzJ`zS1$Qs_nv)SDxB`Jcjbr;WywF@TG>?
z&+tqPoA8Plw&0a8Y{M&KXu+?-Z^EZV8-5FyISf1SJB;avU3~lruZwq<n+Wg(4h9v<
zEclzQ`+>(xoy?I*rca|x<0BgJarTAyKu-*0--&DU97Y;0A<{C=e;3j~qN^u|-lc~9
zhc881fh-_25f+gM$P$tYE(N^2f}~Z*GQtXyDtpr!q>iwPq=t~nH_%_M3h48v=tUEq
zw2l#w6zBAbHKYwB#7dE{6~)XI??2aYc7?QwgeamoTzpDA8A#hW-{)j$P0>`MyCSu4
zzV({7FVYSY3rSstu+lCPa?p;_vC`gk&NCq=Jqt+hsfMJd9!6z<YOCEkl11i*%I<|o
zR;W?gJNi9QA-k&)?wQ`(qpB@f9!2cH>h+H`yq{1@3+PeCXYYx_vke*-I82DdGU)tZ
zP|tqVSNl2c44dt6Itpk;AlT^HUhZAd$ikeLy~<q|;(L*QA^Jr+i*GSZYl)7wDb}=A
zu`cQ&`@{J9Q$DZ@Y`FQ7!;XD78t_|on~lY$%cjR>g$;|uUL_-0<&Ru>bYge1+sy~r
zo2Iq7MZVY|1F<E#;)uhu-byr-tbWKInCp6yNA}dbD@69Ad2PN=m6}Kj)Nh(h{u!*a
zw3HKZ5auAkx*}DucFO9gM0$iE_`!e`e1E~jU$h*sh*f1O$K%t<WM-?XI1SPR;KuS|
z>Z{{m9?9Ex>>WPhK+{vHAi+33nLJUD$3Z0TDuURhexQE|BBcY-<FONr-H{*vFI7zN
z*gbaw>CfyAZR~Y2vc_h-GIf~u#ryORQ9s9{>FGpf&ufP}3eL&JEiw>ou|h4})=Huz
z-k>2lw&RT5Aab1eUiPYbV=F#!r+(~su5@Ex?#LM*x=}RQ4&zC*Ac}*<dkla3HTa^A
zfoeJTSg{gc#CAE08#nXzF8Q)&jjq2z3%kw;h7kqRiv{b7W<QE2GvfY<)EIJXE4^_g
zs-$J!kvsMsXThfWqLP~u-Zn!;cB^^z)+Q}0g6}%rG;6JmR=!oOXojF$GqetQ{{d0;
B18e{Q

diff --git a/object_classifiers/train_obj_classifier.py b/object_classifiers/train_obj_classifier.py
index a54e846..19b48a2 100644
--- a/object_classifiers/train_obj_classifier.py
+++ b/object_classifiers/train_obj_classifier.py
@@ -57,29 +57,58 @@ def conv2d(x, W):
 def max_pool_2x2(x):
     return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
 
+def max_pool_4x4(x):
+    return tf.nn.max_pool(x, ksize=[1, 4, 4, 1], strides=[1, 4, 4, 1], padding='SAME')
+
 def placeholder_inputs():
     # Specify placeholder_inputs
-    x = tf.placeholder(tf.float32, shape=[None, 100, 100, 3])
+    x = tf.placeholder(tf.float32, shape=[None, 25, 25, 3])
     y = tf.placeholder(tf.float32, shape=[None, 4])
     keep_prob = tf.placeholder(tf.float32)
     return x, y, keep_prob
 
-def comp_graph(x, y, keep_prob):
+def comp_graph_v_1(x, y, keep_prob):
     # Specify computation graph
     W_conv1 = weight_variable([5, 5, 3, 10])
     b_conv1 = bias_variable([10])
     
     h_conv1 = tf.nn.relu(conv2d(x, W_conv1) + b_conv1)
+    
     h_pool1 = max_pool_2x2(h_conv1)
     #print(tf.Tensor.get_shape(h_pool1))
     
-    W_fc1 = weight_variable([50*50*10, 4])
+    W_fc1 = weight_variable([13*13*10, 4])
     b_fc1 = bias_variable([4])
     
-    h_pool1_flat = tf.reshape(h_pool1, [-1, 50*50*10])
+    h_pool1_flat = tf.reshape(h_pool1, [-1, 13*13*10])
     h_pool1_flat_drop = tf.nn.dropout(h_pool1_flat, keep_prob)
     
-    y_pred = tf.nn.softmax(tf.matmul(h_pool1_flat,W_fc1) + b_fc1)
+    y_pred = tf.nn.softmax(tf.matmul(h_pool1_flat_drop,W_fc1) + b_fc1)
+    
+    return y_pred
+
+def comp_graph_v_2(x, y, keep_prob):
+    # Specify computation graph
+    W_conv1 = weight_variable([5, 5, 3, 4])
+    b_conv1 = bias_variable([4])
+    
+    h_conv1 = tf.nn.relu(conv2d(x, W_conv1) + b_conv1)
+    h_pool1 = max_pool_2x2(h_conv1)
+    h_conv1_drop = tf.nn.dropout(h_pool1, keep_prob)
+    
+    W_conv2 = weight_variable([3, 3, 4, 8])
+    b_conv2 = bias_variable([8])
+    
+    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
+    h_pool2 = max_pool_2x2(h_conv2)
+
+    W_fc1 = weight_variable([7*7*8, 4])
+    b_fc1 = bias_variable([4])
+    
+    h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*8])
+    h_pool2_flat_drop = tf.nn.dropout(h_pool2_flat, keep_prob)
+    
+    y_pred = tf.nn.softmax(tf.matmul(h_pool2_flat_drop,W_fc1) + b_fc1)
     
     return y_pred
 
@@ -97,7 +126,7 @@ def train():
     sess = tf.InteractiveSession()
 
     x, y, keep_prob = placeholder_inputs()
-    y_pred = comp_graph(x, y, keep_prob)
+    y_pred = comp_graph_v_2(x, y, keep_prob)
 
     # Specify loss
     cross_entropy = -tf.reduce_sum(y*tf.log(y_pred))
@@ -112,14 +141,21 @@ def train():
     merged = tf.merge_all_summaries()
     #writer = tf.train.SummaryWriter("/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier", graph_def=tf.GraphDef())
 
+    # Output dir
+    outdir = '/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier_v_2/'
+    if not os.path.exists(outdir):
+        os.mkdir(outdir)
+
     # Training Data
+    img_width = 75
+    img_height = 75
     train_json_filename = '/home/tanmay/Code/GenVQA/GenVQA/shapes_dataset/train_anno.json'
     image_dir = '/home/tanmay/Code/GenVQA/GenVQA/shapes_dataset/images'
-    mean_image = shape_data_loader.mean_image(train_json_filename, image_dir, 1000, 100)
-    np.save('/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier/mean_image.npy', mean_image)
+    mean_image = shape_data_loader.mean_image(train_json_filename, image_dir, 1000, 100, img_height, img_width)
+    np.save(os.path.join(outdir, 'mean_image.npy'), mean_image)
 
     # Val Data
-    val_batch = shape_data_loader.obj_mini_batch_loader(train_json_filename, image_dir, mean_image, 9501, 499)
+    val_batch = shape_data_loader.obj_mini_batch_loader(train_json_filename, image_dir, mean_image, 9501, 499, img_height, img_width)
     feed_dict_val={x: val_batch[0], y: val_batch[1], keep_prob: 1.0}
     
     # Session Saver
@@ -127,15 +163,15 @@ def train():
 
     # Start Training
     sess.run(tf.initialize_all_variables())
-    batch_size = 100
-    max_epoch = 10
-    max_iter = 95
+    batch_size = 10
+    max_epoch = 2
+    max_iter = 950
     val_acc_array_iter = np.empty([max_iter*max_epoch])
     val_acc_array_epoch = np.zeros([max_epoch])
     train_acc_array_epoch = np.zeros([max_epoch])
     for epoch in range(max_epoch):
         for i in range(max_iter):
-            train_batch = shape_data_loader.obj_mini_batch_loader(train_json_filename, image_dir, mean_image, 1+i*batch_size, batch_size)
+            train_batch = shape_data_loader.obj_mini_batch_loader(train_json_filename, image_dir, mean_image, 1+i*batch_size, batch_size, img_height, img_width)
             feed_dict_train={x: train_batch[0], y: train_batch[1], keep_prob: 0.5}
 
             _, current_train_batch_acc = sess.run([train_step, accuracy], feed_dict=feed_dict_train)
@@ -143,15 +179,15 @@ def train():
             train_acc_array_epoch[epoch] =  train_acc_array_epoch[epoch] + current_train_batch_acc
             val_acc_array_iter[i+epoch*max_iter] = accuracy.eval(feed_dict_val)
             print('Step: {}  Val Accuracy: {}'.format(i+1+epoch*max_iter, val_acc_array_iter[i+epoch*max_iter]))
-            plot_accuracy(np.arange(0,i+1+epoch*max_iter)+1, val_acc_array_iter[0:i+1+epoch*max_iter], xlim=[1, max_epoch*max_iter], ylim=[0, 1.], savePath='/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier/valAcc_vs_iter.pdf')
+            plot_accuracy(np.arange(0,i+1+epoch*max_iter)+1, val_acc_array_iter[0:i+1+epoch*max_iter], xlim=[1, max_epoch*max_iter], ylim=[0, 1.], savePath=os.path.join(outdir,'valAcc_vs_iter.pdf'))
             
             
         train_acc_array_epoch[epoch] = train_acc_array_epoch[epoch] / max_iter 
         val_acc_array_epoch[epoch] = val_acc_array_iter[i+epoch*max_iter]
         
-        plot_accuracies(xdata=np.arange(0,epoch+1)+1, ydata_train=train_acc_array_epoch[0:epoch+1], ydata_val=val_acc_array_epoch[0:epoch+1], xlim=[1, max_epoch], ylim=[0, 1.], savePath='/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier/acc_vs_epoch.pdf')
+        plot_accuracies(xdata=np.arange(0,epoch+1)+1, ydata_train=train_acc_array_epoch[0:epoch+1], ydata_val=val_acc_array_epoch[0:epoch+1], xlim=[1, max_epoch], ylim=[0, 1.], savePath=os.path.join(outdir,'acc_vs_epoch.pdf'))
 
-        save_path = saver.save(sess, '/home/tanmay/Code/GenVQA/Exp_Results/Shape_Classifier/obj_classifier_{}.ckpt'.format(epoch))
+        save_path = saver.save(sess, os.path.join(outdir,'obj_classifier_{}.ckpt'.format(epoch)))
             
     
     sess.close()
diff --git a/object_classifiers/train_obj_classifier.pyc b/object_classifiers/train_obj_classifier.pyc
index cd5b5cafd0d303334de0fb93f98f0f61611f70e1..550d73d4e5c289d02061f24457f8bc52c638aaee 100644
GIT binary patch
delta 2260
zcmZuyO>7%g5T3W|_%HrR?DbEa|FqDwl$4|;EhV%lsc0oiRk@T_Rl|~a9edp-cCy<N
z)Lj`VJ#e5x)sqn7ia1mps6ayE0yiYCMS=@RzzHFQ1c)0FeDl^O3DoxcJa692yqS65
zzP)|;?%bgMM>2l!&+lHUQ0EiE|Fam*?{^2koU#&ho9M~_SxF_7s5(eiimqw2@heqJ
zv_&M=h%7-DCet5@ccSuk^kSlX5D{#_5RyMf$9u|;LzczZdg#dJkvVxMc1H}z!T1;A
zr2H}dwTR14dmkH1Dtm^%@v-l*^lli?kz^=Q56?hbJ}fs9UoE5{R+_6Vv$nC+s+%{>
z>F1zv3;=0Gk=91Fgr32FQRK7<t)fGp!RWzQz;Nb;4EJ7aYt+!G5g{*3o<>_5`4Mta
zlcs(;Ss|*0XiJbEC0D4aP}3N>fp3W19_1cocP~Z-wvLmFh;e1w+cm{Dz1?jRYMXYH
zES+lD89qt11E#?aDdiSrA1h2&T$v%k^p0@pZn(a#GRP}VULSc$l~F3lsGq!k98@og
zKWB3rZ1kj!Lt-(?5Mfa5pJwR|oT2@%q#Vl=SSash7-pDcc#>g1!xY0&hFOMV3{L?j
zx%1@%48akybey66G~*n@GXOUMsGF7M+RbUx+Gw_1&gqg;%qV$zha4CN`nuU_Hm}d@
z2r#1pluvN%liRVCnYnU($#wgQ4D7b82|VWIXl!x%e*~Mkf*{TqphGZ`0JRj=lq6S@
z_xreAlibmu(0Tc2>a7Xzi5zeljt9huUSVkzqYp!I@q77A>dm&>ON|6IlFT8{!TEq#
zlj=;>lcqDVKo}pC2$7#4t4Gl$O${7(f=`^O67US<3zH8P^;09GVj+5#!&w1UQ%n!x
z!r6dxo4vghSy%^sKvV>XFx@~DFHKf24S0fPSxi9GXciZPr_-zt@)0${)X4Fj;Dc$H
z=9RL*8dM{2arhas(&T5!>LcG!c@@dS-dQqgqOG2RoKg?4uJafm4hq<*DbVJy=9bt7
zkxwnAHNQxkrv$5vKs77;0qR!PEe!oZm<$;Oz;=NpRbZ*TU14dvLe#<)4t7f7Ya+rA
zqJ;>9*h^f+6FEfwFnI;?is;KQ6#{V%mJK-eaY|lJZLaDy3UgML8bho>D+_Q#-fg6{
z%V+?nbNY-stm+@(>~`531*|Lg+(XvJErS=)B7`<LVP%CAV;MaS$QIB6hon(^OVh>r
z197Qah(Wn-A-nVP@?7bhJ4#k@M{-4#O(!V}oi3=n0`<6s!^!VbbQA0(w+2o>G&{5z
z_alV`?k^A-+}f4=km_WSo6L4NjuBe_9A$umW6VBb5$-*{ug=x}y7$|I7naYTbdJj(
zvd0p5Z_FIBpU0AO0*WE|L?j|V%%tV5%!?tu0c0#Y9nm1zpXhQfdnUv;nfxp}G0hL}
zQl(<vbj)h2S*cI8tQ98@WplY<R<18Oj=f@6H_Ti9ROMRBogA0GF%~`ogYsGVo^eWC
zkUtqidpUN~arw$<ExC2PG>EF3w&OZ@s}QYTv+ND2=Vqg9(P-Lh@>uTe5xzZ?k5hG=
z|6Z)~@|WBrN6T!&(N?P)wW_7W<_#C&+!(a>YR!Drwy<NoGpkqaTHTdn`F&#P<1_h}
zMf@^Wd<^HjykE#IW<;MJ7QuH!jOycJkDk`E9Zg<j@Xg}8U+mGwwTu{qw2pUhUTbMn
zy34N^(_CG$*GzMA2oqQ5*snR|tN53t;uWzhZxj!SSLFM}eWgQCmQ}kJSUSsaf#I_J
Zw)nc3l#2r+Nj(;c8L?6<7CW5~{{ZnHsnq}g

delta 1339
zcmZuw&2Jk;6#u>5#L4<2{?2;+k=Fg7p-44NAsnzEg{Y)JBjiR!LeQ1taUDCPcC#x~
z#I90d4oD!OYN&t<XSjj-2XLuCT#JN|=z$XlZWY|9)V>+pB2igsX5akYoA-XdnKw7<
zADt<ge<z~9-TM7f3*&E!|0_JaKW>(OF1H<sIJOP!-b1^9AwX9FLAbb>xSgRdXXW?N
z4@t;pVn0${{u}$1EO|Hn)Zr!0&{e6r5BclfvDo^i$<x@9mBd!$H2WJ%@{`0b)hL^-
z{?5Sd>^28mFZ0UF@dTqlN#hX3P5F8HyVTQ+EUK&xz4ffz$b5F>60f}oPcTl>f~kd+
zc{RsFC%P=Bv!Ct>6JZp7481r43qcfO3St_=DTFcj3H0V5A_!s_8Xy&y9LF&vW*}xU
zoJJT&*F+G9u;2uQWo1U$G_WjU2uv)83HU%Sf?i6US2%}&mwRcAW)w6e5LXV9>TJUl
zL;@kZnL-vp8bJ!V4ymXxqtTp##z^)tB!}yNk)KyFyk+&Bdlcqzy{4h`2&z)JH?Ott
z*AzuCk06Ph&6Cn*Yi;&_+Sr_EV--qUL5pFId{Q&$7!aJ1`-13g1EGVkh=A$l`9zAa
zM`oQPdt5(AC^kVxmu0?bu3Q%>^lXK2C7btY^k9s`7xK!QU(m|AANleQhO=Mq&is$B
zD$>ds?4@*S#aM^&Whpxgk>d+D>DrLqoG?!#KVg1=tP}A4<WcvF5cz#<^1A$(g<zpM
zO=JXD1<T7~ThX2%BT9UykF!y4EJDGYYDexH;ppSZtCQmbVF}j;Oq3g{q&kTN_$3y(
z)7Lu>zP*3<!sZ(<dN0Tu_LA(|8?-7{tg_78Wva=a?9+w|`OsdYwSDkwg(Gxsvd-3;
zt(N<q=e7s^*4C+kxZ=G&@j12rX3uTC+w{Edm2P|2{UAKm+8+3eW%+gCaO4bgXuKrv
z6jtfHw4Abh)p=Ha?A%&VPtjLz)@yrS<D?23y?bSymCqNCC)ZSndciyG-A-G`FN!DV
z;_V-bm+6UhMqM7>Ii6s|jSOYXidiy~W_&6_5h_s1EXfOH`?;fZ)Tpw*L{HMDnW1^3
zX&f^BS(U-vX?Cx=u0AQ>RQcZbe1rdePkvQ?i#Fsx<r+2QvC4_mND2EI=hd_>+m#!1
RO#WF}ux6~RWmzk6dIX_(1n>X=

-- 
GitLab