[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Arthur Juliani\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Pix2Pix.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Pix2Pix in Tensorflow\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"This tutorials walks through an implementation of Pix2Pix as described in [Image-to-Image Translation with Conditional Adversarial Networks](https://arxiv.org/abs/1611.07004).\\n\",\n    \"\\n\",\n    \"This specific implementation is designed to 'remaster' black-and-white frames from films with 4:3 aspect ratio into full-color and 16:9 aspect ratio frames. \\n\",\n    \"\\n\",\n    \"To learn more about the Pix2Pix framework, and the images that can be generated using this framework, see my [Medium post](https://medium.com/p/f4d551fa0503).\\n\",\n    \"\\n\",\n    \"This notebook requires the additional helper.py file, which can be obtained [here]()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"#Import the libraries we will need.\\n\",\n    \"import tensorflow as tf\\n\",\n    \"import numpy as np\\n\",\n    \"import matplotlib.pyplot as plt\\n\",\n    \"import tensorflow.contrib.slim as slim\\n\",\n    \"import os\\n\",\n    \"import scipy.misc\\n\",\n    \"import scipy\\n\",\n    \"from PIL import Image\\n\",\n    \"from glob import glob\\n\",\n    \"import os\\n\",\n    \"from helper import *\\n\",\n    \"%matplotlib inline\\n\",\n    \"\\n\",\n    \"#Size of image frames\\n\",\n    \"height = 144\\n\",\n    \"width = 256\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Defining the Adversarial Networks\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Generator Network\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def generator(c):\\n\",\n    \"    with tf.variable_scope('generator'):\\n\",\n    \"        #Encoder\\n\",\n    \"        enc0 = slim.conv2d(c,64,[3,3],padding=\\\"SAME\\\",\\n\",\n    \"            biases_initializer=None,activation_fn=lrelu,\\n\",\n    \"            weights_initializer=initializer)\\n\",\n    \"        enc0 = tf.space_to_depth(enc0,2)\\n\",\n    \"        \\n\",\n    \"        enc1 = slim.conv2d(enc0,128,[3,3],padding=\\\"SAME\\\",\\n\",\n    \"            activation_fn=lrelu,normalizer_fn=slim.batch_norm,\\n\",\n    \"            weights_initializer=initializer)\\n\",\n    \"        enc1 = tf.space_to_depth(enc1,2)\\n\",\n    \"\\n\",\n    \"        enc2 = slim.conv2d(enc1,128,[3,3],padding=\\\"SAME\\\",\\n\",\n    \"            normalizer_fn=slim.batch_norm,activation_fn=lrelu,\\n\",\n    \"            weights_initializer=initializer)\\n\",\n    \"        enc2 = tf.space_to_depth(enc2,2)\\n\",\n    \"\\n\",\n    \"        enc3 = slim.conv2d(enc2,256,[3,3],padding=\\\"SAME\\\",\\n\",\n    \"            normalizer_fn=slim.batch_norm,activation_fn=lrelu,\\n\",\n    \"            weights_initializer=initializer)\\n\",\n    \"        enc3 = tf.space_to_depth(enc3,2)\\n\",\n    \"        \\n\",\n    \"        #Decoder\\n\",\n    \"        gen0 = slim.conv2d(\\n\",\n    \"            enc3,num_outputs=256,kernel_size=[3,3],\\n\",\n    \"            padding=\\\"SAME\\\",normalizer_fn=slim.batch_norm,\\n\",\n    \"            activation_fn=tf.nn.elu, weights_initializer=initializer)\\n\",\n    \"        gen0 = tf.depth_to_space(gen0,2)\\n\",\n    \"\\n\",\n    \"        gen1 = slim.conv2d(\\n\",\n    \"            tf.concat([gen0,enc2],3),num_outputs=256,kernel_size=[3,3],\\n\",\n    \"            padding=\\\"SAME\\\",normalizer_fn=slim.batch_norm,\\n\",\n    \"            activation_fn=tf.nn.elu,weights_initializer=initializer)\\n\",\n    \"        gen1 = tf.depth_to_space(gen1,2)\\n\",\n    \"\\n\",\n    \"        gen2 = slim.conv2d(\\n\",\n    \"            tf.concat([gen1,enc1],3),num_outputs=128,kernel_size=[3,3],\\n\",\n    \"            padding=\\\"SAME\\\",normalizer_fn=slim.batch_norm,\\n\",\n    \"            activation_fn=tf.nn.elu,weights_initializer=initializer)\\n\",\n    \"        gen2 = tf.depth_to_space(gen2,2)\\n\",\n    \"\\n\",\n    \"        gen3 = slim.conv2d(\\n\",\n    \"            tf.concat([gen2,enc0],3),num_outputs=128,kernel_size=[3,3],\\n\",\n    \"            padding=\\\"SAME\\\",normalizer_fn=slim.batch_norm,\\n\",\n    \"            activation_fn=tf.nn.elu, weights_initializer=initializer)\\n\",\n    \"        gen3 = tf.depth_to_space(gen3,2)\\n\",\n    \"        \\n\",\n    \"        g_out = slim.conv2d(\\n\",\n    \"            gen3,num_outputs=3,kernel_size=[1,1],padding=\\\"SAME\\\",\\n\",\n    \"            biases_initializer=None,activation_fn=tf.nn.tanh,\\n\",\n    \"            weights_initializer=initializer)\\n\",\n    \"        return g_out\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Discriminator Network\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"def discriminator(bottom, reuse=False):\\n\",\n    \"    with tf.variable_scope('discriminator'):\\n\",\n    \"        filters = [32,64,128,128]\\n\",\n    \"        \\n\",\n    \"        #Programatically define layers\\n\",\n    \"        for i in range(len(filters)):\\n\",\n    \"            if i == 0:\\n\",\n    \"                layer = slim.conv2d(bottom,filters[i],[3,3],padding=\\\"SAME\\\",scope='d'+str(i),\\n\",\n    \"                    biases_initializer=None,activation_fn=lrelu,stride=[2,2],\\n\",\n    \"                    reuse=reuse,weights_initializer=initializer)\\n\",\n    \"            else:\\n\",\n    \"                layer = slim.conv2d(bottom,filters[i],[3,3],padding=\\\"SAME\\\",scope='d'+str(i),\\n\",\n    \"                    normalizer_fn=slim.batch_norm,activation_fn=lrelu,stride=[2,2],\\n\",\n    \"                    reuse=reuse,weights_initializer=initializer)\\n\",\n    \"            bottom = layer\\n\",\n    \"\\n\",\n    \"        dis_full = slim.fully_connected(slim.flatten(bottom),1024,activation_fn=lrelu,scope='dl',\\n\",\n    \"            reuse=reuse, weights_initializer=initializer)\\n\",\n    \"\\n\",\n    \"        d_out = slim.fully_connected(dis_full,1,activation_fn=tf.nn.sigmoid,scope='do',\\n\",\n    \"            reuse=reuse, weights_initializer=initializer)\\n\",\n    \"        return d_out\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Connecting them together\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"tf.reset_default_graph()\\n\",\n    \"\\n\",\n    \"#This initializaer is used to initialize all the weights of the network.\\n\",\n    \"initializer = tf.truncated_normal_initializer(stddev=0.02)\\n\",\n    \"\\n\",\n    \"#\\n\",\n    \"condition_in = tf.placeholder(shape=[None,height,width,3],dtype=tf.float32)\\n\",\n    \"real_in = tf.placeholder(shape=[None,height,width,3],dtype=tf.float32) #Real images\\n\",\n    \"\\n\",\n    \"Gx = generator(condition_in) #Generates images from random z vectors\\n\",\n    \"Dx = discriminator(real_in) #Produces probabilities for real images\\n\",\n    \"Dg = discriminator(Gx,reuse=True) #Produces probabilities for generator images\\n\",\n    \"\\n\",\n    \"#These functions together define the optimization objective of the GAN.\\n\",\n    \"d_loss = -tf.reduce_mean(tf.log(Dx) + tf.log(1.-Dg)) #This optimizes the discriminator.\\n\",\n    \"#For generator we use traditional GAN objective as well as L1 loss\\n\",\n    \"g_loss = -tf.reduce_mean(tf.log(Dg)) + 100*tf.reduce_mean(tf.abs(Gx - real_in)) #This optimizes the generator.\\n\",\n    \"\\n\",\n    \"#The below code is responsible for applying gradient descent to update the GAN.\\n\",\n    \"trainerD = tf.train.AdamOptimizer(learning_rate=0.0002,beta1=0.5)\\n\",\n    \"trainerG = tf.train.AdamOptimizer(learning_rate=0.002,beta1=0.5)\\n\",\n    \"d_grads = trainerD.compute_gradients(d_loss,slim.get_variables(scope='discriminator'))\\n\",\n    \"g_grads = trainerG.compute_gradients(g_loss, slim.get_variables(scope='generator'))\\n\",\n    \"\\n\",\n    \"update_D = trainerD.apply_gradients(d_grads)\\n\",\n    \"update_G = trainerG.apply_gradients(g_grads)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"source\": [\n    \"## Training the network\\n\",\n    \"Now that we have fully defined our network, it is time to train it!\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"batch_size = 4 #Size of image batch to apply at each iteration.\\n\",\n    \"iterations = 500000 #Total number of iterations to use.\\n\",\n    \"subset_size = 5000 #How many images to load at a time, will vary depending on available resources\\n\",\n    \"frame_directory = './frames' #Directory where training images are located\\n\",\n    \"sample_directory = './samples' #Directory to save sample images from generator in.\\n\",\n    \"model_directory = './model' #Directory to save trained model to.\\n\",\n    \"sample_frequency = 200 #How often to generate sample gif of translated images.\\n\",\n    \"save_frequency = 5000 #How often to save model.\\n\",\n    \"load_model = False #Whether to load the model or begin training from scratch.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false,\n    \"scrolled\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"subset = 0\\n\",\n    \"dataS = sorted(glob(os.path.join(frame_directory, \\\"*.png\\\")))\\n\",\n    \"total_subsets = len(dataS)/subset_size\\n\",\n    \"init = tf.global_variables_initializer()\\n\",\n    \"saver = tf.train.Saver()\\n\",\n    \"with tf.Session() as sess:  \\n\",\n    \"    sess.run(init)\\n\",\n    \"    if load_model == True: \\n\",\n    \"        ckpt = tf.train.get_checkpoint_state(model_directory)\\n\",\n    \"        saver.restore(sess,ckpt.model_checkpoint_path)\\n\",\n    \"\\n\",\n    \"    imagesY,imagesX = loadImages(dataS[0:subset_size],False, np.random.randint(0,2)) #Load a subset of images\\n\",\n    \"    print \\\"Loaded subset \\\" + str(subset)\\n\",\n    \"    draw = range(len(imagesX))\\n\",\n    \"    for i in range(iterations):\\n\",\n    \"        if i % (subset_size/batch_size) != 0 or i == 0:\\n\",\n    \"            batch_index = np.random.choice(draw,size=batch_size,replace=False)\\n\",\n    \"        else:\\n\",\n    \"            subset = np.random.randint(0,total_subsets+1)\\n\",\n    \"            imagesY,imagesX = loadImages(dataS[subset*subset_size:(subset+1)*subset_size],False, np.random.randint(0,2))\\n\",\n    \"            print \\\"Loaded subset \\\" + str(subset)\\n\",\n    \"            draw = range(len(imagesX))\\n\",\n    \"            batch_index = np.random.choice(draw,size=batch_size,replace=False)\\n\",\n    \"        \\n\",\n    \"        ys = (np.reshape(imagesY[batch_index],[batch_size,height,width,3]) - 0.5) * 2.0 #Transform to be between -1 and 1\\n\",\n    \"        xs = (np.reshape(imagesX[batch_index],[batch_size,height,width,3]) - 0.5) * 2.0\\n\",\n    \"        _,dLoss = sess.run([update_D,d_loss],feed_dict={real_in:ys,condition_in:xs}) #Update the discriminator\\n\",\n    \"        _,gLoss = sess.run([update_G,g_loss],feed_dict={real_in:ys,condition_in:xs}) #Update the generator\\n\",\n    \"        if i % sample_frequency == 0:\\n\",\n    \"            print \\\"Gen Loss: \\\" + str(gLoss) + \\\" Disc Loss: \\\" + str(dLoss)\\n\",\n    \"            start_point = np.random.randint(0,len(imagesX)-32)\\n\",\n    \"            xs = (np.reshape(imagesX[start_point:start_point+32],[32,height,width,3]) - 0.5) * 2.0\\n\",\n    \"            ys = (np.reshape(imagesY[start_point:start_point+32],[32,height,width,3]) - 0.5) * 2.0\\n\",\n    \"            sample_G = sess.run(Gx,feed_dict={condition_in:xs}) #Use new z to get sample images from generator.\\n\",\n    \"            allS = np.concatenate([xs,sample_G,ys],axis=1)\\n\",\n    \"            if not os.path.exists(sample_directory):\\n\",\n    \"                os.makedirs(sample_directory)\\n\",\n    \"            #Save sample generator images for viewing training progress.\\n\",\n    \"            make_gif(allS,'./'+sample_directory+'/a_vid'+str(i)+'.gif',\\n\",\n    \"                duration=len(allS)*0.2,true_image=False)\\n\",\n    \"        if i % save_frequency == 0 and i != 0:\\n\",\n    \"            if not os.path.exists(model_directory):\\n\",\n    \"                os.makedirs(model_directory)\\n\",\n    \"            saver.save(sess,model_directory+'/model-'+str(i)+'.cptk')\\n\",\n    \"            print \\\"Saved Model\\\"\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Using a trained network\\n\",\n    \"Once we have a trained model saved, we may want to use it to generate new images, and explore the representation it has learned.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"test_directory = './test_frames' #Directory to load test frames from\\n\",\n    \"subset_size = 5000\\n\",\n    \"batch_size = 60 # Size of image batch to apply at each iteration. Will depend of available resources.\\n\",\n    \"sample_directory = './test_samples' #Directory to save sample images from generator in.\\n\",\n    \"model_directory = './model' #Directory to save trained model to.\\n\",\n    \"load_model = True #Whether to load a saved model.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"dataS = sorted(glob(os.path.join(test_directory, \\\"*.png\\\")))\\n\",\n    \"subset = 0\\n\",\n    \"total_subsets = len(dataS)/subset_size\\n\",\n    \"iterations = subset_size / batch_size #Total number of iterations to use.\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"if not os.path.exists(sample_directory):\\n\",\n    \"    os.makedirs(sample_directory)\\n\",\n    \"\\n\",\n    \"init = tf.global_variables_initializer()\\n\",\n    \"saver = tf.train.Saver()\\n\",\n    \"with tf.Session() as sess:  \\n\",\n    \"    sess.run(init)\\n\",\n    \"    if load_model == True: \\n\",\n    \"        ckpt = tf.train.get_checkpoint_state(model_directory)\\n\",\n    \"        saver.restore(sess,ckpt.model_checkpoint_path)\\n\",\n    \"        for s in range(total_subsets):\\n\",\n    \"            generated_frames = []\\n\",\n    \"            _,imagesX = loadImages(dataS[s*subset_size:s*subset_size+subset_size],False, False) #Load a subset of images\\n\",\n    \"            for i in range(iterations):\\n\",\n    \"                start_point = i * batch_size\\n\",\n    \"                xs = (np.reshape(imagesX[start_point:start_point+batch_size],[batch_size,height,width,3]) - 0.5) * 2.0\\n\",\n    \"                sample_G = sess.run(Gx,feed_dict={condition_in:xs}) #Use new z to get sample images from generator.    \\n\",\n    \"                #allS = np.concatenate([xs,sample_G],axis=2)\\n\",\n    \"                generated_frames.append(sample_G)\\n\",\n    \"            generated_frames = np.vstack(generated_frames)\\n\",\n    \"            for i in range(len(generated_frames)):\\n\",\n    \"                im = Image.fromarray(((generated_frames[i]/2.0 + 0.5) * 256).astype('uint8'))\\n\",\n    \"                im.save('./'+sample_directory+'/frame'+str(s*subset_size + i)+'.png')  \\n\",\n    \"            #make_gif(generated_frames,'./'+sample_directory+'/a_vid'+str(i)+'.gif',\\n\",\n    \"            #    duration=len(generated_frames)/10.0,true_image=False)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 2\",\n   \"language\": \"python\",\n   \"name\": \"python2\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 2\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython2\",\n   \"version\": \"2.7.12\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "README.md",
    "content": "# Pix2Pix-Film\n![alt text](./header.png)\n\nAn implementation of [Pix2Pix](https://arxiv.org/abs/1611.07004) in Tensorflow for use with colorizing and increasing the field of view in frames from classic films. For more information, see my [Medium Post](https://medium.com/p/f4d551fa0503) on the project.\n\nPretrained model available [here](https://drive.google.com/open?id=0B8x0IeJAaBccNFVQMkQ0QW15TjQ). It was trained using Alfred Hitchcock films, so it generalizes best to similar movies.\n\nTo generate frames, use `ffmpeg` to resize and create `.png` frames from video.\n\n* To resize video: `ffmpeg -strict -2 -i input.mp4 -vf scale=256:144 output.mp4 -strict -2`\n\n* To generate frames: ` ffmpeg -i output.mp4 -vf fps=10 ./frames/out%6d.png`\n\nIn order to 'remaster' frames, run Pix2Pix iPython notebook.\n\nTo convert 'remastered' frames  back into a video, use:\n`ffmpeg -framerate 10 -i frame%01d.png -c:v libx264 -r 30 -pix_fmt yuv420p out.mp4`\n"
  },
  {
    "path": "helper.py",
    "content": "import numpy as np\nimport random\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nimport scipy.misc\nimport os\nimport csv\nimport itertools\nimport tensorflow.contrib.slim as slim\nfrom PIL import Image\n\n\n#Generates gifs\ndef make_gif(images, fname, duration=2, true_image=False):\n  import moviepy.editor as mpy\n  \n  def make_frame(t):\n    try:\n      x = images[int(len(images)/duration*t)]\n    except:\n      x = images[-1]\n\n    if true_image:\n      return x.astype(np.uint8)\n    else:\n      return ((x+1)/2*255).astype(np.uint8)\n  \n  clip = mpy.VideoClip(make_frame, duration=duration)\n  clip.write_gif(fname, fps = len(images) / duration,verbose=False)\n\n#Function loads images from list of files. bw_bool is True when the source images are originally greyscale and 4:3\n#Flip determines whether images should be flipped \ndef loadImages(data,bw_bool,flip):\n    images = []\n    images_bw = []\n    if bw_bool == False:\n        for myFile in data:\n            img = Image.open(myFile)\n            bw = np.max(img,2)\n            bw = np.stack([bw,bw,bw],2)\n            bw[:,:40,:] = 0\n            bw[:,-40:,:] = 0\n            if flip == False:\n                images.append(np.array(img))\n                images_bw.append(bw)\n            else:\n                img_flip = np.fliplr(img)\n                images.append(img_flip)\n                bw_flip = np.fliplr(bw)\n                images_bw.append(bw_flip)\n        images = np.array(images)\n        images = images.astype('float32')\n        images = images / 256\n        images_bw = np.array(images_bw)\n        images_bw = images_bw.astype('float32')\n        images_bw = images_bw / 256\n        return images,images_bw\n    else:\n        for myFile in data:\n            img = Image.open(myFile)\n            bw = img.resize((196,144))\n            bw = np.max(bw,2)\n            bw = np.stack([bw,bw,bw],2)\n            bw_w = np.zeros([144,256,3])\n            bw_w[:,30:-30,:] = bw\n            bw_w[:,:40,:] = 0\n            bw_w[:,-40:,:] = 0\n        images.append(bw_w)\n        images = np.array(images)\n        images = images.astype('float32')\n        images = images / 256\n        return images\n\n#This function performns a leaky relu activation, which is needed for the discriminator network.\ndef lrelu(x, leak=0.2, name=\"lrelu\"):\n     with tf.variable_scope(name):\n         f1 = 0.5 * (1 + leak)\n         f2 = 0.5 * (1 - leak)\n         return f1 * x + f2 * abs(x)\n    \n#The below functions are taken from carpdem20's implementation https://github.com/carpedm20/DCGAN-tensorflow\n#They allow for saving sample images from the generator to follow progress\ndef save_images(images, size, image_path):\n    return imsave(inverse_transform(images), size, image_path)\n\ndef imsave(images, size, path):\n    return scipy.misc.imsave(path, merge(images, size))\n\ndef inverse_transform(images):\n    return (images+1.)/2.\n\ndef merge(images, size):\n    h, w = images.shape[1], images.shape[2]\n    img = np.zeros((h * size[0], w * size[1],3))\n\n    for idx, image in enumerate(images):\n        i = idx % size[1]\n        j = idx / size[1]\n        img[j*h:j*h+h, i*w:i*w+w,:] = image\n\n    return img\n"
  }
]