├── Project Proposal with summary of papers reviewed.pdf ├── README.md ├── conv_helper.py ├── libs ├── __pycache__ │ ├── utils.cpython-34.pyc │ ├── utils.cpython-35.pyc │ ├── vgg16.cpython-34.pyc │ └── vgg16.cpython-35.pyc ├── utils.py └── vgg16.py ├── main.py ├── model.py ├── paper.pdf ├── result1.PNG ├── result2.png ├── result3.PNG ├── static ├── css │ ├── 4-col-portfolio.css │ ├── bootstrap.css │ └── bootstrap.min.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ └── jquery.js ├── output.png └── style.css ├── templates └── index.html ├── test.py ├── train.py └── utils.py /Project Proposal with summary of papers reviewed.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/Project Proposal with summary of papers reviewed.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Denoising with GAN 2 | [Paper](https://uofi.box.com/shared/static/s16nc93x8j6ctd0ercx9juf5mqmqx4bp.pdf) | [Video](https://www.youtube.com/watch?v=Yh_Bsoe-Qj4) 3 | 4 | ## Introduction 5 | 6 | Animation movie companies like Pixar and Dreamworks render their 3d scenes using a technique called Pathtracing which enables them to create high quality photorealistic frames. Pathtracing involves shooting 1000’s of rays into a pixel randomly(Monte Carlo) which will then hit the objects in the scene and based on the reflective property of the object the rays reflect or refract or get absorbed. The colors returned by these rays are averaged to get the color of the pixel and this process is repeated for all the pixels. Due to the computational complexity it might take 8-16 hours to render a single frame. 7 | 8 | We are proposing a neural network based solution for reducing 8-16 hours to a couple of seconds using a Generative Adversarial Network. The main idea behind this proposed method is to render using small number of samples per pixel (let say 4 spp or 8 spp instead of 32K spp) and pass the noisy image to our network, which will generate a photorealistic image with high quality. 9 | 10 | # Demo Video [Link](https://www.youtube.com/watch?v=Yh_Bsoe-Qj4) 11 | 12 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/Yh_Bsoe-Qj4/0.jpg)](https://www.youtube.com/watch?v=Yh_Bsoe-Qj4) 13 | 14 | 15 | 16 | #### Table of Contents 17 | 18 | * [Installation](#installation) 19 | * [Running](#running) 20 | * [Dataset](#dataset) 21 | * [Hyperparameters](#hyperparameter) 22 | * [Results](#results) 23 | * [Improvements](#improvements) 24 | * [Credits](#credits) 25 | 26 | ## Installation 27 | 28 | To run the project you will need: 29 | * python 3.5 30 | * tensorflow (v1.1 or v1.0) 31 | * PIL 32 | * [CKPT FILE](https://uofi.box.com/shared/static/21a5jwdiqpnx24c50cyolwzwycnr3fwe.gz) 33 | * [Dataset](https://uofi.box.com/shared/static/gy0t3vgwtlk1933xbtz1zvhlakkdac3n.zip) 34 | 35 | ## Running 36 | 37 | Once you have all the depenedencies ready, do the folowing: 38 | 39 | Download the dataset extract it to a folder named 'dataset' (ONLY if you want to train, not needed to run). 40 | 41 | Extract the CKPT files to a folder named 'Checkpoints' 42 | 43 | Run main.py -- python3 main.py 44 | 45 | Go to the browser, if you are running it on a server then [ip-address]:8888, if you are on your local machine then localhost:8888 46 | 47 | ## Dataset 48 | We picked random 40 images from pixar movies, added gaussian noise of different standard deviation, 5 sets of 5 different standard deviation making a total of 1000 images for the training set. For validation we used 10 images completely different from the training set and added gaussian noise. For testing we had both added gaussian images and real noisy images. 49 | 50 | ## Hyperparameters 51 | * Number of iterations - 10K 52 | * Adversarial Loss Factor - 0.5 53 | * Pixel Loss Factor - 1.0 54 | * Feature Loss Factor - 1.0 55 | * Smoothness Loss Factor - 0.0001 56 | 57 | ## Results 58 | 3D rendering test data: 59 | alt text 60 | 61 | Real noise images: 62 | alt text 63 | 64 | CT-Scan: 65 | alt text 66 | 67 | 68 | ## Improvements 69 | 70 | * Increase the num of iteration to 100K. 71 | * Train the network for different noises. 72 | * Make it work on a real-time app. 73 | 74 | ## Credits 75 | 76 | * [SRGAN](https://arxiv.org/pdf/1609.04802.pdf) 77 | * [Image De-raining using conditional generative adversarial network](https://arxiv.org/pdf/1701.05957.pdf) 78 | * [Creating photorealistic images from gameboy camera](http://www.pinchofintelligence.com/photorealistic-neural-network-gameboy/) 79 | * [CS20SI](cs20si.stanford.edu) 80 | * [CS231n](https://cs231n.github.io/) 81 | -------------------------------------------------------------------------------- /conv_helper.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import tensorflow.contrib.slim as slim 3 | 4 | from utils import * 5 | 6 | def conv_layer(input_image, ksize, in_channels, out_channels, stride, scope_name, activation_function=lrelu, reuse=False): 7 | with tf.variable_scope(scope_name): 8 | filter = tf.Variable(tf.random_normal([ksize, ksize, in_channels, out_channels], stddev=0.03)) 9 | output = tf.nn.conv2d(input_image, filter, strides=[1, stride, stride, 1], padding='SAME') 10 | output = slim.batch_norm(output) 11 | if activation_function: 12 | output = activation_function(output) 13 | return output, filter 14 | 15 | def residual_layer(input_image, ksize, in_channels, out_channels, stride, scope_name): 16 | with tf.variable_scope(scope_name): 17 | output, filter = conv_layer(input_image, ksize, in_channels, out_channels, stride, scope_name+"_conv1") 18 | output, filter = conv_layer(output, ksize, out_channels, out_channels, stride, scope_name+"_conv2") 19 | output = tf.add(output, tf.identity(input_image)) 20 | return output, filter 21 | 22 | def transpose_deconvolution_layer(input_tensor, used_weights, new_shape, stride, scope_name): 23 | with tf.varaible_scope(scope_name): 24 | output = tf.nn.conv2d_transpose(input_tensor, used_weights, output_shape=new_shape, strides=[1, stride, stride, 1], padding='SAME') 25 | output = tf.nn.relu(output) 26 | return output 27 | 28 | def resize_deconvolution_layer(input_tensor, new_shape, scope_name): 29 | with tf.variable_scope(scope_name): 30 | output = tf.image.resize_images(input_tensor, (new_shape[1], new_shape[2]), method=1) 31 | output, unused_weights = conv_layer(output, 3, new_shape[3]*2, new_shape[3], 1, scope_name+"_deconv") 32 | return output 33 | 34 | def deconvolution_layer(input_tensor, new_shape, scope_name): 35 | return resize_deconvolution_layer(input_tensor, new_shape, scope_name) 36 | 37 | def output_between_zero_and_one(output): 38 | output +=1 39 | return output/2 40 | -------------------------------------------------------------------------------- /libs/__pycache__/utils.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/libs/__pycache__/utils.cpython-34.pyc -------------------------------------------------------------------------------- /libs/__pycache__/utils.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/libs/__pycache__/utils.cpython-35.pyc -------------------------------------------------------------------------------- /libs/__pycache__/vgg16.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/libs/__pycache__/vgg16.cpython-34.pyc -------------------------------------------------------------------------------- /libs/__pycache__/vgg16.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/libs/__pycache__/vgg16.cpython-35.pyc -------------------------------------------------------------------------------- /libs/utils.py: -------------------------------------------------------------------------------- 1 | """Utilities used in the Kadenze Academy Course on Deep Learning w/ Tensorflow. 2 | 3 | Creative Applications of Deep Learning w/ Tensorflow. 4 | Kadenze, Inc. 5 | Parag K. Mital 6 | 7 | Copyright Parag K. Mital, June 2016. 8 | """ 9 | import matplotlib.pyplot as plt 10 | import tensorflow as tf 11 | import urllib 12 | import numpy as np 13 | import zipfile 14 | import os 15 | from scipy.io import wavfile 16 | 17 | 18 | def download(path): 19 | """Use urllib to download a file. 20 | 21 | Parameters 22 | ---------- 23 | path : str 24 | Url to download 25 | 26 | Returns 27 | ------- 28 | path : str 29 | Location of downloaded file. 30 | """ 31 | import os 32 | from six.moves import urllib 33 | 34 | fname = path.split('/')[-1] 35 | if os.path.exists(fname): 36 | return fname 37 | 38 | print('Downloading ' + path) 39 | 40 | def progress(count, block_size, total_size): 41 | if count % 20 == 0: 42 | print('Downloaded %02.02f/%02.02f MB' % ( 43 | count * block_size / 1024.0 / 1024.0, 44 | total_size / 1024.0 / 1024.0), end='\r') 45 | 46 | filepath, _ = urllib.request.urlretrieve( 47 | path, filename=fname, reporthook=progress) 48 | return filepath 49 | 50 | 51 | def download_and_extract_tar(path, dst): 52 | """Download and extract a tar file. 53 | 54 | Parameters 55 | ---------- 56 | path : str 57 | Url to tar file to download. 58 | dst : str 59 | Location to save tar file contents. 60 | """ 61 | import tarfile 62 | filepath = download(path) 63 | if not os.path.exists(dst): 64 | os.makedirs(dst) 65 | tarfile.open(filepath, 'r:gz').extractall(dst) 66 | 67 | 68 | def download_and_extract_zip(path, dst): 69 | """Download and extract a zip file. 70 | 71 | Parameters 72 | ---------- 73 | path : str 74 | Url to zip file to download. 75 | dst : str 76 | Location to save zip file contents. 77 | """ 78 | import zipfile 79 | filepath = download(path) 80 | if not os.path.exists(dst): 81 | os.makedirs(dst) 82 | zf = zipfile.ZipFile(file=filepath) 83 | zf.extractall(dst) 84 | 85 | 86 | def load_audio(filename, b_normalize=True): 87 | """Load the audiofile at the provided filename using scipy.io.wavfile. 88 | 89 | Optionally normalizes the audio to the maximum value. 90 | 91 | Parameters 92 | ---------- 93 | filename : str 94 | File to load. 95 | b_normalize : bool, optional 96 | Normalize to the maximum value. 97 | """ 98 | sr, s = wavfile.read(filename) 99 | if b_normalize: 100 | s = s.astype(np.float32) 101 | s = (s / np.max(np.abs(s))) 102 | s -= np.mean(s) 103 | return s 104 | 105 | 106 | def corrupt(x): 107 | """Take an input tensor and add uniform masking. 108 | 109 | Parameters 110 | ---------- 111 | x : Tensor/Placeholder 112 | Input to corrupt. 113 | Returns 114 | ------- 115 | x_corrupted : Tensor 116 | 50 pct of values corrupted. 117 | """ 118 | return tf.mul(x, tf.cast(tf.random_uniform(shape=tf.shape(x), 119 | minval=0, 120 | maxval=2, 121 | dtype=tf.int32), tf.float32)) 122 | 123 | 124 | def interp(l, r, n_samples): 125 | """Intepolate between the arrays l and r, n_samples times. 126 | 127 | Parameters 128 | ---------- 129 | l : np.ndarray 130 | Left edge 131 | r : np.ndarray 132 | Right edge 133 | n_samples : int 134 | Number of samples 135 | 136 | Returns 137 | ------- 138 | arr : np.ndarray 139 | Inteporalted array 140 | """ 141 | return np.array([ 142 | l + step_i / (n_samples - 1) * (r - l) 143 | for step_i in range(n_samples)]) 144 | 145 | 146 | def make_latent_manifold(corners, n_samples): 147 | """Create a 2d manifold out of the provided corners: n_samples * n_samples. 148 | 149 | Parameters 150 | ---------- 151 | corners : list of np.ndarray 152 | The four corners to intepolate. 153 | n_samples : int 154 | Number of samples to use in interpolation. 155 | 156 | Returns 157 | ------- 158 | arr : np.ndarray 159 | Stacked array of all 2D interpolated samples 160 | """ 161 | left = interp(corners[0], corners[1], n_samples) 162 | right = interp(corners[2], corners[3], n_samples) 163 | 164 | embedding = [] 165 | for row_i in range(n_samples): 166 | embedding.append(interp(left[row_i], right[row_i], n_samples)) 167 | return np.vstack(embedding) 168 | 169 | 170 | def imcrop_tosquare(img): 171 | """Make any image a square image. 172 | 173 | Parameters 174 | ---------- 175 | img : np.ndarray 176 | Input image to crop, assumed at least 2d. 177 | 178 | Returns 179 | ------- 180 | crop : np.ndarray 181 | Cropped image. 182 | """ 183 | size = np.min(img.shape[:2]) 184 | extra = img.shape[:2] - size 185 | crop = img 186 | for i in np.flatnonzero(extra): 187 | crop = np.take(crop, extra[i] // 2 + np.r_[:size], axis=i) 188 | return crop 189 | 190 | 191 | def slice_montage(montage, img_h, img_w, n_imgs): 192 | """Slice a montage image into n_img h x w images. 193 | 194 | Performs the opposite of the montage function. Takes a montage image and 195 | slices it back into a N x H x W x C image. 196 | 197 | Parameters 198 | ---------- 199 | montage : np.ndarray 200 | Montage image to slice. 201 | img_h : int 202 | Height of sliced image 203 | img_w : int 204 | Width of sliced image 205 | n_imgs : int 206 | Number of images to slice 207 | 208 | Returns 209 | ------- 210 | sliced : np.ndarray 211 | Sliced images as 4d array. 212 | """ 213 | sliced_ds = [] 214 | for i in range(int(np.sqrt(n_imgs))): 215 | for j in range(int(np.sqrt(n_imgs))): 216 | sliced_ds.append(montage[ 217 | 1 + i + i * img_h:1 + i + (i + 1) * img_h, 218 | 1 + j + j * img_w:1 + j + (j + 1) * img_w]) 219 | return np.array(sliced_ds) 220 | 221 | 222 | def montage(images, saveto='montage.png'): 223 | """Draw all images as a montage separated by 1 pixel borders. 224 | 225 | Also saves the file to the destination specified by `saveto`. 226 | 227 | Parameters 228 | ---------- 229 | images : numpy.ndarray 230 | Input array to create montage of. Array should be: 231 | batch x height x width x channels. 232 | saveto : str 233 | Location to save the resulting montage image. 234 | 235 | Returns 236 | ------- 237 | m : numpy.ndarray 238 | Montage image. 239 | """ 240 | if isinstance(images, list): 241 | images = np.array(images) 242 | img_h = images.shape[1] 243 | img_w = images.shape[2] 244 | n_plots = int(np.ceil(np.sqrt(images.shape[0]))) 245 | if len(images.shape) == 4 and images.shape[3] == 3: 246 | m = np.ones( 247 | (images.shape[1] * n_plots + n_plots + 1, 248 | images.shape[2] * n_plots + n_plots + 1, 3)) * 0.5 249 | else: 250 | m = np.ones( 251 | (images.shape[1] * n_plots + n_plots + 1, 252 | images.shape[2] * n_plots + n_plots + 1)) * 0.5 253 | for i in range(n_plots): 254 | for j in range(n_plots): 255 | this_filter = i * n_plots + j 256 | if this_filter < images.shape[0]: 257 | this_img = images[this_filter] 258 | m[1 + i + i * img_h:1 + i + (i + 1) * img_h, 259 | 1 + j + j * img_w:1 + j + (j + 1) * img_w] = this_img 260 | plt.imsave(arr=m, fname=saveto) 261 | return m 262 | 263 | 264 | def montage_filters(W): 265 | """Draws all filters (n_input * n_output filters) as a 266 | montage image separated by 1 pixel borders. 267 | 268 | Parameters 269 | ---------- 270 | W : Tensor 271 | Input tensor to create montage of. 272 | 273 | Returns 274 | ------- 275 | m : numpy.ndarray 276 | Montage image. 277 | """ 278 | W = np.reshape(W, [W.shape[0], W.shape[1], 1, W.shape[2] * W.shape[3]]) 279 | n_plots = int(np.ceil(np.sqrt(W.shape[-1]))) 280 | m = np.ones( 281 | (W.shape[0] * n_plots + n_plots + 1, 282 | W.shape[1] * n_plots + n_plots + 1)) * 0.5 283 | for i in range(n_plots): 284 | for j in range(n_plots): 285 | this_filter = i * n_plots + j 286 | if this_filter < W.shape[-1]: 287 | m[1 + i + i * W.shape[0]:1 + i + (i + 1) * W.shape[0], 288 | 1 + j + j * W.shape[1]:1 + j + (j + 1) * W.shape[1]] = ( 289 | np.squeeze(W[:, :, :, this_filter])) 290 | return m 291 | 292 | 293 | def get_celeb_files(dst='img_align_celeba', max_images=100): 294 | """Download the first 100 images of the celeb dataset. 295 | 296 | Files will be placed in a directory 'img_align_celeba' if one 297 | doesn't exist. 298 | 299 | Returns 300 | ------- 301 | files : list of strings 302 | Locations to the first 100 images of the celeb net dataset. 303 | """ 304 | # Create a directory 305 | if not os.path.exists(dst): 306 | os.mkdir(dst) 307 | 308 | # Now perform the following 100 times: 309 | for img_i in range(1, max_images + 1): 310 | 311 | # create a string using the current loop counter 312 | f = '000%03d.jpg' % img_i 313 | 314 | if not os.path.exists(os.path.join(dst, f)): 315 | 316 | # and get the url with that string appended the end 317 | url = 'https://s3.amazonaws.com/cadl/celeb-align/' + f 318 | 319 | # We'll print this out to the console so we can see how far we've gone 320 | print(url, end='\r') 321 | 322 | # And now download the url to a location inside our new directory 323 | urllib.request.urlretrieve(url, os.path.join(dst, f)) 324 | 325 | files = [os.path.join(dst, file_i) 326 | for file_i in os.listdir(dst) 327 | if '.jpg' in file_i][:max_images] 328 | return files 329 | 330 | 331 | def get_celeb_imgs(max_images=100): 332 | """Load the first `max_images` images of the celeb dataset. 333 | 334 | Returns 335 | ------- 336 | imgs : list of np.ndarray 337 | List of the first 100 images from the celeb dataset 338 | """ 339 | return [plt.imread(f_i) for f_i in get_celeb_files(max_images=max_images)] 340 | 341 | 342 | def gauss(mean, stddev, ksize): 343 | """Use Tensorflow to compute a Gaussian Kernel. 344 | 345 | Parameters 346 | ---------- 347 | mean : float 348 | Mean of the Gaussian (e.g. 0.0). 349 | stddev : float 350 | Standard Deviation of the Gaussian (e.g. 1.0). 351 | ksize : int 352 | Size of kernel (e.g. 16). 353 | 354 | Returns 355 | ------- 356 | kernel : np.ndarray 357 | Computed Gaussian Kernel using Tensorflow. 358 | """ 359 | g = tf.Graph() 360 | with tf.Session(graph=g): 361 | x = tf.linspace(-3.0, 3.0, ksize) 362 | z = (tf.exp(tf.neg(tf.pow(x - mean, 2.0) / 363 | (2.0 * tf.pow(stddev, 2.0)))) * 364 | (1.0 / (stddev * tf.sqrt(2.0 * 3.1415)))) 365 | return z.eval() 366 | 367 | 368 | def gauss2d(mean, stddev, ksize): 369 | """Use Tensorflow to compute a 2D Gaussian Kernel. 370 | 371 | Parameters 372 | ---------- 373 | mean : float 374 | Mean of the Gaussian (e.g. 0.0). 375 | stddev : float 376 | Standard Deviation of the Gaussian (e.g. 1.0). 377 | ksize : int 378 | Size of kernel (e.g. 16). 379 | 380 | Returns 381 | ------- 382 | kernel : np.ndarray 383 | Computed 2D Gaussian Kernel using Tensorflow. 384 | """ 385 | z = gauss(mean, stddev, ksize) 386 | g = tf.Graph() 387 | with tf.Session(graph=g): 388 | z_2d = tf.matmul(tf.reshape(z, [ksize, 1]), tf.reshape(z, [1, ksize])) 389 | return z_2d.eval() 390 | 391 | 392 | def convolve(img, kernel): 393 | """Use Tensorflow to convolve a 4D image with a 4D kernel. 394 | 395 | Parameters 396 | ---------- 397 | img : np.ndarray 398 | 4-dimensional image shaped N x H x W x C 399 | kernel : np.ndarray 400 | 4-dimensional image shape K_H, K_W, C_I, C_O corresponding to the 401 | kernel's height and width, the number of input channels, and the 402 | number of output channels. Note that C_I should = C. 403 | 404 | Returns 405 | ------- 406 | result : np.ndarray 407 | Convolved result. 408 | """ 409 | g = tf.Graph() 410 | with tf.Session(graph=g): 411 | convolved = tf.nn.conv2d(img, kernel, strides=[1, 1, 1, 1], padding='SAME') 412 | res = convolved.eval() 413 | return res 414 | 415 | 416 | def gabor(ksize=32): 417 | """Use Tensorflow to compute a 2D Gabor Kernel. 418 | 419 | Parameters 420 | ---------- 421 | ksize : int, optional 422 | Size of kernel. 423 | 424 | Returns 425 | ------- 426 | gabor : np.ndarray 427 | Gabor kernel with ksize x ksize dimensions. 428 | """ 429 | g = tf.Graph() 430 | with tf.Session(graph=g): 431 | z_2d = gauss2d(0.0, 1.0, ksize) 432 | ones = tf.ones((1, ksize)) 433 | ys = tf.sin(tf.linspace(-3.0, 3.0, ksize)) 434 | ys = tf.reshape(ys, [ksize, 1]) 435 | wave = tf.matmul(ys, ones) 436 | gabor = tf.mul(wave, z_2d) 437 | return gabor.eval() 438 | 439 | 440 | def build_submission(filename, file_list, optional_file_list=()): 441 | """Helper utility to check homework assignment submissions and package them. 442 | 443 | Parameters 444 | ---------- 445 | filename : str 446 | Output zip file name 447 | file_list : tuple 448 | Tuple of files to include 449 | """ 450 | # check each file exists 451 | for part_i, file_i in enumerate(file_list): 452 | if not os.path.exists(file_i): 453 | print('\nYou are missing the file {}. '.format(file_i) + 454 | 'It does not look like you have completed Part {}.'.format( 455 | part_i + 1)) 456 | 457 | def zipdir(path, zf): 458 | for root, dirs, files in os.walk(path): 459 | for file in files: 460 | # make sure the files are part of the necessary file list 461 | if file.endswith(file_list) or file.endswith(optional_file_list): 462 | zf.write(os.path.join(root, file)) 463 | 464 | # create a zip file with the necessary files 465 | zipf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED) 466 | zipdir('.', zipf) 467 | zipf.close() 468 | print('Your assignment zip file has been created!') 469 | print('Now submit the file:\n{}\nto Kadenze for grading!'.format( 470 | os.path.abspath(filename))) 471 | 472 | 473 | def normalize(a, s=0.1): 474 | '''Normalize the image range for visualization''' 475 | return np.uint8(np.clip( 476 | (a - a.mean()) / max(a.std(), 1e-4) * s + 0.5, 477 | 0, 1) * 255) 478 | 479 | 480 | # %% 481 | def weight_variable(shape, **kwargs): 482 | '''Helper function to create a weight variable initialized with 483 | a normal distribution 484 | Parameters 485 | ---------- 486 | shape : list 487 | Size of weight variable 488 | ''' 489 | if isinstance(shape, list): 490 | initial = tf.random_normal(tf.pack(shape), mean=0.0, stddev=0.01) 491 | initial.set_shape(shape) 492 | else: 493 | initial = tf.random_normal(shape, mean=0.0, stddev=0.01) 494 | return tf.Variable(initial, **kwargs) 495 | 496 | 497 | # %% 498 | def bias_variable(shape, **kwargs): 499 | '''Helper function to create a bias variable initialized with 500 | a constant value. 501 | Parameters 502 | ---------- 503 | shape : list 504 | Size of weight variable 505 | ''' 506 | if isinstance(shape, list): 507 | initial = tf.random_normal(tf.pack(shape), mean=0.0, stddev=0.01) 508 | initial.set_shape(shape) 509 | else: 510 | initial = tf.random_normal(shape, mean=0.0, stddev=0.01) 511 | return tf.Variable(initial, **kwargs) 512 | 513 | 514 | def binary_cross_entropy(z, x, name=None): 515 | """Binary Cross Entropy measures cross entropy of a binary variable. 516 | 517 | loss(x, z) = - sum_i (x[i] * log(z[i]) + (1 - x[i]) * log(1 - z[i])) 518 | 519 | Parameters 520 | ---------- 521 | z : tf.Tensor 522 | A `Tensor` of the same type and shape as `x`. 523 | x : tf.Tensor 524 | A `Tensor` of type `float32` or `float64`. 525 | """ 526 | with tf.variable_scope(name or 'bce'): 527 | eps = 1e-12 528 | return (-(x * tf.log(z + eps) + 529 | (1. - x) * tf.log(1. - z + eps))) 530 | 531 | 532 | def conv2d(x, n_output, 533 | k_h=5, k_w=5, d_h=2, d_w=2, 534 | padding='SAME', name='conv2d', reuse=None): 535 | """Helper for creating a 2d convolution operation. 536 | 537 | Parameters 538 | ---------- 539 | x : tf.Tensor 540 | Input tensor to convolve. 541 | n_output : int 542 | Number of filters. 543 | k_h : int, optional 544 | Kernel height 545 | k_w : int, optional 546 | Kernel width 547 | d_h : int, optional 548 | Height stride 549 | d_w : int, optional 550 | Width stride 551 | padding : str, optional 552 | Padding type: "SAME" or "VALID" 553 | name : str, optional 554 | Variable scope 555 | 556 | Returns 557 | ------- 558 | op : tf.Tensor 559 | Output of convolution 560 | """ 561 | with tf.variable_scope(name or 'conv2d', reuse=reuse): 562 | W = tf.get_variable( 563 | name='W', 564 | shape=[k_h, k_w, x.get_shape()[-1], n_output], 565 | initializer=tf.contrib.layers.xavier_initializer_conv2d()) 566 | 567 | conv = tf.nn.conv2d( 568 | name='conv', 569 | input=x, 570 | filter=W, 571 | strides=[1, d_h, d_w, 1], 572 | padding=padding) 573 | 574 | b = tf.get_variable( 575 | name='b', 576 | shape=[n_output], 577 | initializer=tf.constant_initializer(0.0)) 578 | 579 | h = tf.nn.bias_add( 580 | name='h', 581 | value=conv, 582 | bias=b) 583 | 584 | return h, W 585 | 586 | 587 | def deconv2d(x, n_output_h, n_output_w, n_output_ch, n_input_ch=None, 588 | k_h=5, k_w=5, d_h=2, d_w=2, 589 | padding='SAME', name='deconv2d', reuse=None): 590 | """Deconvolution helper. 591 | 592 | Parameters 593 | ---------- 594 | x : tf.Tensor 595 | Input tensor to convolve. 596 | n_output_h : int 597 | Height of output 598 | n_output_w : int 599 | Width of output 600 | n_output_ch : int 601 | Number of filters. 602 | k_h : int, optional 603 | Kernel height 604 | k_w : int, optional 605 | Kernel width 606 | d_h : int, optional 607 | Height stride 608 | d_w : int, optional 609 | Width stride 610 | padding : str, optional 611 | Padding type: "SAME" or "VALID" 612 | name : str, optional 613 | Variable scope 614 | 615 | Returns 616 | ------- 617 | op : tf.Tensor 618 | Output of deconvolution 619 | """ 620 | with tf.variable_scope(name or 'deconv2d', reuse=reuse): 621 | W = tf.get_variable( 622 | name='W', 623 | shape=[k_h, k_h, n_output_ch, n_input_ch or x.get_shape()[-1]], 624 | initializer=tf.contrib.layers.xavier_initializer_conv2d()) 625 | 626 | conv = tf.nn.conv2d_transpose( 627 | name='conv_t', 628 | value=x, 629 | filter=W, 630 | output_shape=tf.pack( 631 | [tf.shape(x)[0], n_output_h, n_output_w, n_output_ch]), 632 | strides=[1, d_h, d_w, 1], 633 | padding=padding) 634 | 635 | conv.set_shape([None, n_output_h, n_output_w, n_output_ch]) 636 | 637 | b = tf.get_variable( 638 | name='b', 639 | shape=[n_output_ch], 640 | initializer=tf.constant_initializer(0.0)) 641 | 642 | h = tf.nn.bias_add(name='h', value=conv, bias=b) 643 | 644 | return h, W 645 | 646 | 647 | def lrelu(features, leak=0.2): 648 | """Leaky rectifier. 649 | 650 | Parameters 651 | ---------- 652 | features : tf.Tensor 653 | Input to apply leaky rectifier to. 654 | leak : float, optional 655 | Percentage of leak. 656 | 657 | Returns 658 | ------- 659 | op : tf.Tensor 660 | Resulting output of applying leaky rectifier activation. 661 | """ 662 | f1 = 0.5 * (1 + leak) 663 | f2 = 0.5 * (1 - leak) 664 | return f1 * features + f2 * abs(features) 665 | 666 | 667 | def linear(x, n_output, name=None, activation=None, reuse=None): 668 | """Fully connected layer. 669 | 670 | Parameters 671 | ---------- 672 | x : tf.Tensor 673 | Input tensor to connect 674 | n_output : int 675 | Number of output neurons 676 | name : None, optional 677 | Scope to apply 678 | 679 | Returns 680 | ------- 681 | h, W : tf.Tensor, tf.Tensor 682 | Output of fully connected layer and the weight matrix 683 | """ 684 | if len(x.get_shape()) != 2: 685 | x = flatten(x, reuse=reuse) 686 | 687 | n_input = x.get_shape().as_list()[1] 688 | 689 | with tf.variable_scope(name or "fc", reuse=reuse): 690 | W = tf.get_variable( 691 | name='W', 692 | shape=[n_input, n_output], 693 | dtype=tf.float32, 694 | initializer=tf.contrib.layers.xavier_initializer()) 695 | 696 | b = tf.get_variable( 697 | name='b', 698 | shape=[n_output], 699 | dtype=tf.float32, 700 | initializer=tf.constant_initializer(0.0)) 701 | 702 | h = tf.nn.bias_add( 703 | name='h', 704 | value=tf.matmul(x, W), 705 | bias=b) 706 | 707 | if activation: 708 | h = activation(h) 709 | 710 | return h, W 711 | 712 | 713 | def flatten(x, name=None, reuse=None): 714 | """Flatten Tensor to 2-dimensions. 715 | 716 | Parameters 717 | ---------- 718 | x : tf.Tensor 719 | Input tensor to flatten. 720 | name : None, optional 721 | Variable scope for flatten operations 722 | 723 | Returns 724 | ------- 725 | flattened : tf.Tensor 726 | Flattened tensor. 727 | """ 728 | with tf.variable_scope('flatten'): 729 | dims = x.get_shape().as_list() 730 | if len(dims) == 4: 731 | flattened = tf.reshape( 732 | x, 733 | shape=[-1, dims[1] * dims[2] * dims[3]]) 734 | elif len(dims) == 2 or len(dims) == 1: 735 | flattened = x 736 | else: 737 | raise ValueError('Expected n dimensions of 1, 2 or 4. Found:', 738 | len(dims)) 739 | 740 | return flattened 741 | 742 | 743 | def to_tensor(x): 744 | """Convert 2 dim Tensor to a 4 dim Tensor ready for convolution. 745 | 746 | Performs the opposite of flatten(x). If the tensor is already 4-D, this 747 | returns the same as the input, leaving it unchanged. 748 | 749 | Parameters 750 | ---------- 751 | x : tf.Tesnor 752 | Input 2-D tensor. If 4-D already, left unchanged. 753 | 754 | Returns 755 | ------- 756 | x : tf.Tensor 757 | 4-D representation of the input. 758 | 759 | Raises 760 | ------ 761 | ValueError 762 | If the tensor is not 2D or already 4D. 763 | """ 764 | if len(x.get_shape()) == 2: 765 | n_input = x.get_shape().as_list()[1] 766 | x_dim = np.sqrt(n_input) 767 | if x_dim == int(x_dim): 768 | x_dim = int(x_dim) 769 | x_tensor = tf.reshape( 770 | x, [-1, x_dim, x_dim, 1], name='reshape') 771 | elif np.sqrt(n_input / 3) == int(np.sqrt(n_input / 3)): 772 | x_dim = int(np.sqrt(n_input / 3)) 773 | x_tensor = tf.reshape( 774 | x, [-1, x_dim, x_dim, 3], name='reshape') 775 | else: 776 | x_tensor = tf.reshape( 777 | x, [-1, 1, 1, n_input], name='reshape') 778 | elif len(x.get_shape()) == 4: 779 | x_tensor = x 780 | else: 781 | raise ValueError('Unsupported input dimensions') 782 | return x_tensor 783 | -------------------------------------------------------------------------------- /libs/vgg16.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creative Applications of Deep Learning w/ Tensorflow. 3 | Kadenze, Inc. 4 | Copyright Parag K. Mital, June 2016. 5 | """ 6 | import tensorflow as tf 7 | import json 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | from skimage.transform import resize as imresize 11 | from .utils import download 12 | 13 | 14 | def get_vgg_face_model(): 15 | download('https://s3.amazonaws.com/cadl/models/vgg_face.tfmodel') 16 | with open("vgg_face.tfmodel", mode='rb') as f: 17 | graph_def = tf.GraphDef() 18 | try: 19 | graph_def.ParseFromString(f.read()) 20 | except: 21 | print('try adding PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python ' + 22 | 'to environment. e.g.:\n' + 23 | 'PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python ipython\n' + 24 | 'See here for info: ' + 25 | 'https://github.com/tensorflow/tensorflow/issues/582') 26 | 27 | download('https://s3.amazonaws.com/cadl/models/vgg_face.json') 28 | labels = json.load(open('vgg_face.json')) 29 | 30 | return { 31 | 'graph_def': graph_def, 32 | 'labels': labels, 33 | 'preprocess': preprocess, 34 | 'deprocess': deprocess 35 | } 36 | 37 | 38 | def get_vgg_model(): 39 | download('https://s3.amazonaws.com/cadl/models/vgg16.tfmodel') 40 | with open("vgg16.tfmodel", mode='rb') as f: 41 | graph_def = tf.GraphDef() 42 | try: 43 | graph_def.ParseFromString(f.read()) 44 | except: 45 | print('try adding PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python ' + 46 | 'to environment. e.g.:\n' + 47 | 'PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python ipython\n' + 48 | 'See here for info: ' + 49 | 'https://github.com/tensorflow/tensorflow/issues/582') 50 | 51 | download('https://s3.amazonaws.com/cadl/models/synset.txt') 52 | with open('synset.txt') as f: 53 | labels = [(idx, l.strip()) for idx, l in enumerate(f.readlines())] 54 | 55 | return { 56 | 'graph_def': graph_def, 57 | 'labels': labels, 58 | 'preprocess': preprocess, 59 | 'deprocess': deprocess 60 | } 61 | 62 | 63 | def preprocess(img, crop=True, resize=True, dsize=(224, 224)): 64 | if img.dtype == np.uint8: 65 | img = img / 255.0 66 | 67 | if crop: 68 | short_edge = min(img.shape[:2]) 69 | yy = int((img.shape[0] - short_edge) / 2) 70 | xx = int((img.shape[1] - short_edge) / 2) 71 | crop_img = img[yy: yy + short_edge, xx: xx + short_edge] 72 | else: 73 | crop_img = img 74 | 75 | if resize: 76 | norm_img = imresize(crop_img, dsize, preserve_range=True) 77 | else: 78 | norm_img = crop_img 79 | 80 | return (norm_img).astype(np.float32) 81 | 82 | 83 | def deprocess(img): 84 | return np.clip(img * 255, 0, 255).astype(np.uint8) 85 | # return ((img / np.max(np.abs(img))) * 127.5 + 86 | # 127.5).astype(np.uint8) 87 | 88 | 89 | def test_vgg(): 90 | """Loads the VGG network and applies it to a test image. 91 | """ 92 | with tf.Session() as sess: 93 | net = get_vgg_model() 94 | tf.import_graph_def(net['graph_def'], name='vgg') 95 | g = tf.get_default_graph() 96 | names = [op.name for op in g.get_operations()] 97 | input_name = names[0] + ':0' 98 | x = g.get_tensor_by_name(input_name) 99 | softmax = g.get_tensor_by_name(names[-2] + ':0') 100 | 101 | og = plt.imread('bosch.png') 102 | img = preprocess(og)[np.newaxis, ...] 103 | res = np.squeeze(softmax.eval(feed_dict={ 104 | x: img, 105 | 'vgg/dropout_1/random_uniform:0': [[1.0]], 106 | 'vgg/dropout/random_uniform:0': [[1.0]]})) 107 | print([(res[idx], net['labels'][idx]) 108 | for idx in res.argsort()[-5:][::-1]]) 109 | 110 | """Let's visualize the network's gradient activation 111 | when backpropagated to the original input image. This 112 | is effectively telling us which pixels contribute to the 113 | predicted class or given neuron""" 114 | features = [name for name in names if 'BiasAdd' in name.split()[-1]] 115 | from math import sqrt, ceil 116 | n_plots = ceil(sqrt(len(features) + 1)) 117 | fig, axs = plt.subplots(n_plots, n_plots) 118 | plot_i = 0 119 | axs[0][0].imshow(img[0]) 120 | for feature_i, featurename in enumerate(features): 121 | plot_i += 1 122 | feature = g.get_tensor_by_name(featurename + ':0') 123 | neuron = tf.reduce_max(feature, 1) 124 | saliency = tf.gradients(tf.reduce_sum(neuron), x) 125 | neuron_idx = tf.arg_max(feature, 1) 126 | this_res = sess.run([saliency[0], neuron_idx], feed_dict={ 127 | x: img, 128 | 'vgg/dropout_1/random_uniform:0': [[1.0]], 129 | 'vgg/dropout/random_uniform:0': [[1.0]]}) 130 | 131 | grad = this_res[0][0] / np.max(np.abs(this_res[0])) 132 | ax = axs[plot_i // n_plots][plot_i % n_plots] 133 | ax.imshow((grad * 127.5 + 127.5).astype(np.uint8)) 134 | ax.set_title(featurename) 135 | 136 | """Deep Dreaming takes the backpropagated gradient activations 137 | and simply adds it to the image, running the same process again 138 | and again in a loop. There are many tricks one can add to this 139 | idea, such as infinitely zooming into the image by cropping and 140 | scaling, adding jitter by randomly moving the image around, or 141 | adding constraints on the total activations.""" 142 | og = plt.imread('street.png') 143 | crop = 2 144 | img = preprocess(og)[np.newaxis, ...] 145 | layer = g.get_tensor_by_name(features[3] + ':0') 146 | n_els = layer.get_shape().as_list()[1] 147 | neuron_i = np.random.randint(1000) 148 | layer_vec = np.zeros((1, n_els)) 149 | layer_vec[0, neuron_i] = 1 150 | neuron = tf.reduce_max(layer, 1) 151 | saliency = tf.gradients(tf.reduce_sum(neuron), x) 152 | for it_i in range(3): 153 | print(it_i) 154 | this_res = sess.run(saliency[0], feed_dict={ 155 | x: img, 156 | layer: layer_vec, 157 | 'vgg/dropout_1/random_uniform:0': [[1.0]], 158 | 'vgg/dropout/random_uniform:0': [[1.0]]}) 159 | grad = this_res[0] / np.mean(np.abs(grad)) 160 | img = img[:, crop:-crop - 1, crop:-crop - 1, :] 161 | img = imresize(img[0], (224, 224))[np.newaxis] 162 | img += grad 163 | plt.imshow(deprocess(img[0])) 164 | 165 | 166 | def test_vgg_face(): 167 | """Loads the VGG network and applies it to a test image. 168 | """ 169 | with tf.Session() as sess: 170 | net = get_vgg_face_model() 171 | x = tf.placeholder(tf.float32, [1, 224, 224, 3], name='x') 172 | tf.import_graph_def(net['graph_def'], name='vgg', 173 | input_map={'Placeholder:0': x}) 174 | g = tf.get_default_graph() 175 | names = [op.name for op in g.get_operations()] 176 | 177 | og = plt.imread('bricks.png')[..., :3] 178 | img = preprocess(og)[np.newaxis, ...] 179 | plt.imshow(img[0]) 180 | plt.show() 181 | 182 | """Let's visualize the network's gradient activation 183 | when backpropagated to the original input image. This 184 | is effectively telling us which pixels contribute to the 185 | predicted class or given neuron""" 186 | features = [name for name in names if 'BiasAdd' in name.split()[-1]] 187 | from math import sqrt, ceil 188 | n_plots = ceil(sqrt(len(features) + 1)) 189 | fig, axs = plt.subplots(n_plots, n_plots) 190 | plot_i = 0 191 | axs[0][0].imshow(img[0]) 192 | for feature_i, featurename in enumerate(features): 193 | plot_i += 1 194 | feature = g.get_tensor_by_name(featurename + ':0') 195 | neuron = tf.reduce_max(feature, 1) 196 | saliency = tf.gradients(tf.reduce_sum(neuron), x) 197 | neuron_idx = tf.arg_max(feature, 1) 198 | this_res = sess.run([saliency[0], neuron_idx], feed_dict={x: img}) 199 | 200 | grad = this_res[0][0] / np.max(np.abs(this_res[0])) 201 | ax = axs[plot_i // n_plots][plot_i % n_plots] 202 | ax.imshow((grad * 127.5 + 127.5).astype(np.uint8)) 203 | ax.set_title(featurename) 204 | plt.waitforbuttonpress() 205 | 206 | """Deep Dreaming takes the backpropagated gradient activations 207 | and simply adds it to the image, running the same process again 208 | and again in a loop. There are many tricks one can add to this 209 | idea, such as infinitely zooming into the image by cropping and 210 | scaling, adding jitter by randomly moving the image around, or 211 | adding constraints on the total activations.""" 212 | og = plt.imread('street.png') 213 | crop = 2 214 | img = preprocess(og)[np.newaxis, ...] 215 | layer = g.get_tensor_by_name(features[3] + ':0') 216 | n_els = layer.get_shape().as_list()[1] 217 | neuron_i = np.random.randint(1000) 218 | layer_vec = np.zeros((1, n_els)) 219 | layer_vec[0, neuron_i] = 1 220 | neuron = tf.reduce_max(layer, 1) 221 | saliency = tf.gradients(tf.reduce_sum(neuron), x) 222 | for it_i in range(3): 223 | print(it_i) 224 | this_res = sess.run(saliency[0], feed_dict={ 225 | x: img, 226 | layer: layer_vec, 227 | 'vgg/dropout_1/random_uniform:0': [[1.0]], 228 | 'vgg/dropout/random_uniform:0': [[1.0]]}) 229 | grad = this_res[0] / np.mean(np.abs(grad)) 230 | img = img[:, crop:-crop - 1, crop:-crop - 1, :] 231 | img = imresize(img[0], (224, 224))[np.newaxis] 232 | img += grad 233 | plt.imshow(deprocess(img[0])) 234 | 235 | if __name__ == '__main__': 236 | test_vgg_face() 237 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, jsonify, send_file 2 | import numpy as np 3 | import scipy.misc 4 | import base64 5 | from io import BytesIO 6 | from test import * 7 | import time 8 | 9 | app = Flask(__name__) 10 | 11 | @app.route('/') 12 | def index(): 13 | return render_template("index.html") 14 | 15 | 16 | @app.route('/denoisify', methods=['GET', 'POST']) 17 | def denoisify(): 18 | if request.method == "POST": 19 | inputImg = request.files['file'] 20 | outputImg = denoise(inputImg) 21 | scipy.misc.imsave('static/output.png', outputImg) 22 | return jsonify(result="Success") 23 | 24 | 25 | if __name__=="__main__": 26 | app.run(host="0.0.0.0",port="80") 27 | -------------------------------------------------------------------------------- /model.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | import tensorflow.contrib.slim as slim 4 | 5 | 6 | from utils import * 7 | from conv_helper import * 8 | 9 | 10 | def generator(input): 11 | conv1, conv1_weights = conv_layer(input, 9, 3, 32, 1, "g_conv1") 12 | conv2, conv2_weights = conv_layer(conv1, 3, 32, 64, 1, "g_conv2") 13 | conv3, conv3_weights = conv_layer(conv2, 3, 64, 128, 1, "g_conv3") 14 | 15 | res1, res1_weights = residual_layer(conv3, 3, 128, 128, 1, "g_res1") 16 | res2, res2_weights = residual_layer(res1, 3, 128, 128, 1, "g_res2") 17 | res3, res3_weights = residual_layer(res2, 3, 128, 128, 1, "g_res3") 18 | 19 | deconv1 = deconvolution_layer(res3, [BATCH_SIZE, 128, 128, 64], 'g_deconv1') 20 | deconv2 = deconvolution_layer(deconv1, [BATCH_SIZE, 256, 256, 32], "g_deconv2") 21 | 22 | deconv2 = deconv2 + conv1 23 | 24 | conv4, conv4_weights = conv_layer(deconv2, 9, 32, 3, 1, "g_conv5", activation_function=tf.nn.tanh) 25 | 26 | conv4 = conv4 + input 27 | output = output_between_zero_and_one(conv4) 28 | 29 | return output 30 | 31 | def discriminator(input, reuse=False): 32 | conv1, conv1_weights = conv_layer(input, 4, 3, 48, 2, "d_conv1", reuse=reuse) 33 | conv2, conv2_weights = conv_layer(conv1, 4, 48, 96, 2, "d_conv2", reuse=reuse) 34 | conv3, conv3_weights = conv_layer(conv2, 4, 96, 192, 2, "d_conv3", reuse=reuse) 35 | conv4, conv4_weights = conv_layer(conv3, 4, 192, 384, 1, "d_conv4", reuse=reuse) 36 | conv5, conv5_weights = conv_layer(conv4, 4, 384, 1, 1, "d_conv5", activation_function=tf.nn.sigmoid, reuse=reuse) 37 | 38 | return conv5 39 | -------------------------------------------------------------------------------- /paper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/paper.pdf -------------------------------------------------------------------------------- /result1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/result1.PNG -------------------------------------------------------------------------------- /result2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/result2.png -------------------------------------------------------------------------------- /result3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/result3.PNG -------------------------------------------------------------------------------- /static/css/4-col-portfolio.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - 4 Col Portfolio (http://startbootstrap.com/) 3 | * Copyright 2013-2016 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | 7 | body { 8 | padding-top: 70px; /* Required padding for .navbar-fixed-top. Remove if using .navbar-static-top. Change if height of navigation changes. */ 9 | } 10 | 11 | .portfolio-item { 12 | margin-bottom: 25px; 13 | } 14 | 15 | footer { 16 | margin: 50px 0; 17 | } -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /static/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | 7 | if (typeof jQuery === 'undefined') { 8 | throw new Error('Bootstrap\'s JavaScript requires jQuery') 9 | } 10 | 11 | +function ($) { 12 | 'use strict'; 13 | var version = $.fn.jquery.split(' ')[0].split('.') 14 | if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) { 15 | throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4') 16 | } 17 | }(jQuery); 18 | 19 | /* ======================================================================== 20 | * Bootstrap: transition.js v3.3.7 21 | * http://getbootstrap.com/javascript/#transitions 22 | * ======================================================================== 23 | * Copyright 2011-2016 Twitter, Inc. 24 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 25 | * ======================================================================== */ 26 | 27 | 28 | +function ($) { 29 | 'use strict'; 30 | 31 | // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) 32 | // ============================================================ 33 | 34 | function transitionEnd() { 35 | var el = document.createElement('bootstrap') 36 | 37 | var transEndEventNames = { 38 | WebkitTransition : 'webkitTransitionEnd', 39 | MozTransition : 'transitionend', 40 | OTransition : 'oTransitionEnd otransitionend', 41 | transition : 'transitionend' 42 | } 43 | 44 | for (var name in transEndEventNames) { 45 | if (el.style[name] !== undefined) { 46 | return { end: transEndEventNames[name] } 47 | } 48 | } 49 | 50 | return false // explicit for ie8 ( ._.) 51 | } 52 | 53 | // http://blog.alexmaccaw.com/css-transitions 54 | $.fn.emulateTransitionEnd = function (duration) { 55 | var called = false 56 | var $el = this 57 | $(this).one('bsTransitionEnd', function () { called = true }) 58 | var callback = function () { if (!called) $($el).trigger($.support.transition.end) } 59 | setTimeout(callback, duration) 60 | return this 61 | } 62 | 63 | $(function () { 64 | $.support.transition = transitionEnd() 65 | 66 | if (!$.support.transition) return 67 | 68 | $.event.special.bsTransitionEnd = { 69 | bindType: $.support.transition.end, 70 | delegateType: $.support.transition.end, 71 | handle: function (e) { 72 | if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) 73 | } 74 | } 75 | }) 76 | 77 | }(jQuery); 78 | 79 | /* ======================================================================== 80 | * Bootstrap: alert.js v3.3.7 81 | * http://getbootstrap.com/javascript/#alerts 82 | * ======================================================================== 83 | * Copyright 2011-2016 Twitter, Inc. 84 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 85 | * ======================================================================== */ 86 | 87 | 88 | +function ($) { 89 | 'use strict'; 90 | 91 | // ALERT CLASS DEFINITION 92 | // ====================== 93 | 94 | var dismiss = '[data-dismiss="alert"]' 95 | var Alert = function (el) { 96 | $(el).on('click', dismiss, this.close) 97 | } 98 | 99 | Alert.VERSION = '3.3.7' 100 | 101 | Alert.TRANSITION_DURATION = 150 102 | 103 | Alert.prototype.close = function (e) { 104 | var $this = $(this) 105 | var selector = $this.attr('data-target') 106 | 107 | if (!selector) { 108 | selector = $this.attr('href') 109 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 110 | } 111 | 112 | var $parent = $(selector === '#' ? [] : selector) 113 | 114 | if (e) e.preventDefault() 115 | 116 | if (!$parent.length) { 117 | $parent = $this.closest('.alert') 118 | } 119 | 120 | $parent.trigger(e = $.Event('close.bs.alert')) 121 | 122 | if (e.isDefaultPrevented()) return 123 | 124 | $parent.removeClass('in') 125 | 126 | function removeElement() { 127 | // detach from parent, fire event then clean up data 128 | $parent.detach().trigger('closed.bs.alert').remove() 129 | } 130 | 131 | $.support.transition && $parent.hasClass('fade') ? 132 | $parent 133 | .one('bsTransitionEnd', removeElement) 134 | .emulateTransitionEnd(Alert.TRANSITION_DURATION) : 135 | removeElement() 136 | } 137 | 138 | 139 | // ALERT PLUGIN DEFINITION 140 | // ======================= 141 | 142 | function Plugin(option) { 143 | return this.each(function () { 144 | var $this = $(this) 145 | var data = $this.data('bs.alert') 146 | 147 | if (!data) $this.data('bs.alert', (data = new Alert(this))) 148 | if (typeof option == 'string') data[option].call($this) 149 | }) 150 | } 151 | 152 | var old = $.fn.alert 153 | 154 | $.fn.alert = Plugin 155 | $.fn.alert.Constructor = Alert 156 | 157 | 158 | // ALERT NO CONFLICT 159 | // ================= 160 | 161 | $.fn.alert.noConflict = function () { 162 | $.fn.alert = old 163 | return this 164 | } 165 | 166 | 167 | // ALERT DATA-API 168 | // ============== 169 | 170 | $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) 171 | 172 | }(jQuery); 173 | 174 | /* ======================================================================== 175 | * Bootstrap: button.js v3.3.7 176 | * http://getbootstrap.com/javascript/#buttons 177 | * ======================================================================== 178 | * Copyright 2011-2016 Twitter, Inc. 179 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 180 | * ======================================================================== */ 181 | 182 | 183 | +function ($) { 184 | 'use strict'; 185 | 186 | // BUTTON PUBLIC CLASS DEFINITION 187 | // ============================== 188 | 189 | var Button = function (element, options) { 190 | this.$element = $(element) 191 | this.options = $.extend({}, Button.DEFAULTS, options) 192 | this.isLoading = false 193 | } 194 | 195 | Button.VERSION = '3.3.7' 196 | 197 | Button.DEFAULTS = { 198 | loadingText: 'loading...' 199 | } 200 | 201 | Button.prototype.setState = function (state) { 202 | var d = 'disabled' 203 | var $el = this.$element 204 | var val = $el.is('input') ? 'val' : 'html' 205 | var data = $el.data() 206 | 207 | state += 'Text' 208 | 209 | if (data.resetText == null) $el.data('resetText', $el[val]()) 210 | 211 | // push to event loop to allow forms to submit 212 | setTimeout($.proxy(function () { 213 | $el[val](data[state] == null ? this.options[state] : data[state]) 214 | 215 | if (state == 'loadingText') { 216 | this.isLoading = true 217 | $el.addClass(d).attr(d, d).prop(d, true) 218 | } else if (this.isLoading) { 219 | this.isLoading = false 220 | $el.removeClass(d).removeAttr(d).prop(d, false) 221 | } 222 | }, this), 0) 223 | } 224 | 225 | Button.prototype.toggle = function () { 226 | var changed = true 227 | var $parent = this.$element.closest('[data-toggle="buttons"]') 228 | 229 | if ($parent.length) { 230 | var $input = this.$element.find('input') 231 | if ($input.prop('type') == 'radio') { 232 | if ($input.prop('checked')) changed = false 233 | $parent.find('.active').removeClass('active') 234 | this.$element.addClass('active') 235 | } else if ($input.prop('type') == 'checkbox') { 236 | if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false 237 | this.$element.toggleClass('active') 238 | } 239 | $input.prop('checked', this.$element.hasClass('active')) 240 | if (changed) $input.trigger('change') 241 | } else { 242 | this.$element.attr('aria-pressed', !this.$element.hasClass('active')) 243 | this.$element.toggleClass('active') 244 | } 245 | } 246 | 247 | 248 | // BUTTON PLUGIN DEFINITION 249 | // ======================== 250 | 251 | function Plugin(option) { 252 | return this.each(function () { 253 | var $this = $(this) 254 | var data = $this.data('bs.button') 255 | var options = typeof option == 'object' && option 256 | 257 | if (!data) $this.data('bs.button', (data = new Button(this, options))) 258 | 259 | if (option == 'toggle') data.toggle() 260 | else if (option) data.setState(option) 261 | }) 262 | } 263 | 264 | var old = $.fn.button 265 | 266 | $.fn.button = Plugin 267 | $.fn.button.Constructor = Button 268 | 269 | 270 | // BUTTON NO CONFLICT 271 | // ================== 272 | 273 | $.fn.button.noConflict = function () { 274 | $.fn.button = old 275 | return this 276 | } 277 | 278 | 279 | // BUTTON DATA-API 280 | // =============== 281 | 282 | $(document) 283 | .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { 284 | var $btn = $(e.target).closest('.btn') 285 | Plugin.call($btn, 'toggle') 286 | if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { 287 | // Prevent double click on radios, and the double selections (so cancellation) on checkboxes 288 | e.preventDefault() 289 | // The target component still receive the focus 290 | if ($btn.is('input,button')) $btn.trigger('focus') 291 | else $btn.find('input:visible,button:visible').first().trigger('focus') 292 | } 293 | }) 294 | .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { 295 | $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) 296 | }) 297 | 298 | }(jQuery); 299 | 300 | /* ======================================================================== 301 | * Bootstrap: carousel.js v3.3.7 302 | * http://getbootstrap.com/javascript/#carousel 303 | * ======================================================================== 304 | * Copyright 2011-2016 Twitter, Inc. 305 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 306 | * ======================================================================== */ 307 | 308 | 309 | +function ($) { 310 | 'use strict'; 311 | 312 | // CAROUSEL CLASS DEFINITION 313 | // ========================= 314 | 315 | var Carousel = function (element, options) { 316 | this.$element = $(element) 317 | this.$indicators = this.$element.find('.carousel-indicators') 318 | this.options = options 319 | this.paused = null 320 | this.sliding = null 321 | this.interval = null 322 | this.$active = null 323 | this.$items = null 324 | 325 | this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) 326 | 327 | this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element 328 | .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) 329 | .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) 330 | } 331 | 332 | Carousel.VERSION = '3.3.7' 333 | 334 | Carousel.TRANSITION_DURATION = 600 335 | 336 | Carousel.DEFAULTS = { 337 | interval: 5000, 338 | pause: 'hover', 339 | wrap: true, 340 | keyboard: true 341 | } 342 | 343 | Carousel.prototype.keydown = function (e) { 344 | if (/input|textarea/i.test(e.target.tagName)) return 345 | switch (e.which) { 346 | case 37: this.prev(); break 347 | case 39: this.next(); break 348 | default: return 349 | } 350 | 351 | e.preventDefault() 352 | } 353 | 354 | Carousel.prototype.cycle = function (e) { 355 | e || (this.paused = false) 356 | 357 | this.interval && clearInterval(this.interval) 358 | 359 | this.options.interval 360 | && !this.paused 361 | && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) 362 | 363 | return this 364 | } 365 | 366 | Carousel.prototype.getItemIndex = function (item) { 367 | this.$items = item.parent().children('.item') 368 | return this.$items.index(item || this.$active) 369 | } 370 | 371 | Carousel.prototype.getItemForDirection = function (direction, active) { 372 | var activeIndex = this.getItemIndex(active) 373 | var willWrap = (direction == 'prev' && activeIndex === 0) 374 | || (direction == 'next' && activeIndex == (this.$items.length - 1)) 375 | if (willWrap && !this.options.wrap) return active 376 | var delta = direction == 'prev' ? -1 : 1 377 | var itemIndex = (activeIndex + delta) % this.$items.length 378 | return this.$items.eq(itemIndex) 379 | } 380 | 381 | Carousel.prototype.to = function (pos) { 382 | var that = this 383 | var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) 384 | 385 | if (pos > (this.$items.length - 1) || pos < 0) return 386 | 387 | if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" 388 | if (activeIndex == pos) return this.pause().cycle() 389 | 390 | return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) 391 | } 392 | 393 | Carousel.prototype.pause = function (e) { 394 | e || (this.paused = true) 395 | 396 | if (this.$element.find('.next, .prev').length && $.support.transition) { 397 | this.$element.trigger($.support.transition.end) 398 | this.cycle(true) 399 | } 400 | 401 | this.interval = clearInterval(this.interval) 402 | 403 | return this 404 | } 405 | 406 | Carousel.prototype.next = function () { 407 | if (this.sliding) return 408 | return this.slide('next') 409 | } 410 | 411 | Carousel.prototype.prev = function () { 412 | if (this.sliding) return 413 | return this.slide('prev') 414 | } 415 | 416 | Carousel.prototype.slide = function (type, next) { 417 | var $active = this.$element.find('.item.active') 418 | var $next = next || this.getItemForDirection(type, $active) 419 | var isCycling = this.interval 420 | var direction = type == 'next' ? 'left' : 'right' 421 | var that = this 422 | 423 | if ($next.hasClass('active')) return (this.sliding = false) 424 | 425 | var relatedTarget = $next[0] 426 | var slideEvent = $.Event('slide.bs.carousel', { 427 | relatedTarget: relatedTarget, 428 | direction: direction 429 | }) 430 | this.$element.trigger(slideEvent) 431 | if (slideEvent.isDefaultPrevented()) return 432 | 433 | this.sliding = true 434 | 435 | isCycling && this.pause() 436 | 437 | if (this.$indicators.length) { 438 | this.$indicators.find('.active').removeClass('active') 439 | var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) 440 | $nextIndicator && $nextIndicator.addClass('active') 441 | } 442 | 443 | var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" 444 | if ($.support.transition && this.$element.hasClass('slide')) { 445 | $next.addClass(type) 446 | $next[0].offsetWidth // force reflow 447 | $active.addClass(direction) 448 | $next.addClass(direction) 449 | $active 450 | .one('bsTransitionEnd', function () { 451 | $next.removeClass([type, direction].join(' ')).addClass('active') 452 | $active.removeClass(['active', direction].join(' ')) 453 | that.sliding = false 454 | setTimeout(function () { 455 | that.$element.trigger(slidEvent) 456 | }, 0) 457 | }) 458 | .emulateTransitionEnd(Carousel.TRANSITION_DURATION) 459 | } else { 460 | $active.removeClass('active') 461 | $next.addClass('active') 462 | this.sliding = false 463 | this.$element.trigger(slidEvent) 464 | } 465 | 466 | isCycling && this.cycle() 467 | 468 | return this 469 | } 470 | 471 | 472 | // CAROUSEL PLUGIN DEFINITION 473 | // ========================== 474 | 475 | function Plugin(option) { 476 | return this.each(function () { 477 | var $this = $(this) 478 | var data = $this.data('bs.carousel') 479 | var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) 480 | var action = typeof option == 'string' ? option : options.slide 481 | 482 | if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) 483 | if (typeof option == 'number') data.to(option) 484 | else if (action) data[action]() 485 | else if (options.interval) data.pause().cycle() 486 | }) 487 | } 488 | 489 | var old = $.fn.carousel 490 | 491 | $.fn.carousel = Plugin 492 | $.fn.carousel.Constructor = Carousel 493 | 494 | 495 | // CAROUSEL NO CONFLICT 496 | // ==================== 497 | 498 | $.fn.carousel.noConflict = function () { 499 | $.fn.carousel = old 500 | return this 501 | } 502 | 503 | 504 | // CAROUSEL DATA-API 505 | // ================= 506 | 507 | var clickHandler = function (e) { 508 | var href 509 | var $this = $(this) 510 | var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 511 | if (!$target.hasClass('carousel')) return 512 | var options = $.extend({}, $target.data(), $this.data()) 513 | var slideIndex = $this.attr('data-slide-to') 514 | if (slideIndex) options.interval = false 515 | 516 | Plugin.call($target, options) 517 | 518 | if (slideIndex) { 519 | $target.data('bs.carousel').to(slideIndex) 520 | } 521 | 522 | e.preventDefault() 523 | } 524 | 525 | $(document) 526 | .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) 527 | .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) 528 | 529 | $(window).on('load', function () { 530 | $('[data-ride="carousel"]').each(function () { 531 | var $carousel = $(this) 532 | Plugin.call($carousel, $carousel.data()) 533 | }) 534 | }) 535 | 536 | }(jQuery); 537 | 538 | /* ======================================================================== 539 | * Bootstrap: collapse.js v3.3.7 540 | * http://getbootstrap.com/javascript/#collapse 541 | * ======================================================================== 542 | * Copyright 2011-2016 Twitter, Inc. 543 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 544 | * ======================================================================== */ 545 | 546 | /* jshint latedef: false */ 547 | 548 | +function ($) { 549 | 'use strict'; 550 | 551 | // COLLAPSE PUBLIC CLASS DEFINITION 552 | // ================================ 553 | 554 | var Collapse = function (element, options) { 555 | this.$element = $(element) 556 | this.options = $.extend({}, Collapse.DEFAULTS, options) 557 | this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + 558 | '[data-toggle="collapse"][data-target="#' + element.id + '"]') 559 | this.transitioning = null 560 | 561 | if (this.options.parent) { 562 | this.$parent = this.getParent() 563 | } else { 564 | this.addAriaAndCollapsedClass(this.$element, this.$trigger) 565 | } 566 | 567 | if (this.options.toggle) this.toggle() 568 | } 569 | 570 | Collapse.VERSION = '3.3.7' 571 | 572 | Collapse.TRANSITION_DURATION = 350 573 | 574 | Collapse.DEFAULTS = { 575 | toggle: true 576 | } 577 | 578 | Collapse.prototype.dimension = function () { 579 | var hasWidth = this.$element.hasClass('width') 580 | return hasWidth ? 'width' : 'height' 581 | } 582 | 583 | Collapse.prototype.show = function () { 584 | if (this.transitioning || this.$element.hasClass('in')) return 585 | 586 | var activesData 587 | var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') 588 | 589 | if (actives && actives.length) { 590 | activesData = actives.data('bs.collapse') 591 | if (activesData && activesData.transitioning) return 592 | } 593 | 594 | var startEvent = $.Event('show.bs.collapse') 595 | this.$element.trigger(startEvent) 596 | if (startEvent.isDefaultPrevented()) return 597 | 598 | if (actives && actives.length) { 599 | Plugin.call(actives, 'hide') 600 | activesData || actives.data('bs.collapse', null) 601 | } 602 | 603 | var dimension = this.dimension() 604 | 605 | this.$element 606 | .removeClass('collapse') 607 | .addClass('collapsing')[dimension](0) 608 | .attr('aria-expanded', true) 609 | 610 | this.$trigger 611 | .removeClass('collapsed') 612 | .attr('aria-expanded', true) 613 | 614 | this.transitioning = 1 615 | 616 | var complete = function () { 617 | this.$element 618 | .removeClass('collapsing') 619 | .addClass('collapse in')[dimension]('') 620 | this.transitioning = 0 621 | this.$element 622 | .trigger('shown.bs.collapse') 623 | } 624 | 625 | if (!$.support.transition) return complete.call(this) 626 | 627 | var scrollSize = $.camelCase(['scroll', dimension].join('-')) 628 | 629 | this.$element 630 | .one('bsTransitionEnd', $.proxy(complete, this)) 631 | .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) 632 | } 633 | 634 | Collapse.prototype.hide = function () { 635 | if (this.transitioning || !this.$element.hasClass('in')) return 636 | 637 | var startEvent = $.Event('hide.bs.collapse') 638 | this.$element.trigger(startEvent) 639 | if (startEvent.isDefaultPrevented()) return 640 | 641 | var dimension = this.dimension() 642 | 643 | this.$element[dimension](this.$element[dimension]())[0].offsetHeight 644 | 645 | this.$element 646 | .addClass('collapsing') 647 | .removeClass('collapse in') 648 | .attr('aria-expanded', false) 649 | 650 | this.$trigger 651 | .addClass('collapsed') 652 | .attr('aria-expanded', false) 653 | 654 | this.transitioning = 1 655 | 656 | var complete = function () { 657 | this.transitioning = 0 658 | this.$element 659 | .removeClass('collapsing') 660 | .addClass('collapse') 661 | .trigger('hidden.bs.collapse') 662 | } 663 | 664 | if (!$.support.transition) return complete.call(this) 665 | 666 | this.$element 667 | [dimension](0) 668 | .one('bsTransitionEnd', $.proxy(complete, this)) 669 | .emulateTransitionEnd(Collapse.TRANSITION_DURATION) 670 | } 671 | 672 | Collapse.prototype.toggle = function () { 673 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 674 | } 675 | 676 | Collapse.prototype.getParent = function () { 677 | return $(this.options.parent) 678 | .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') 679 | .each($.proxy(function (i, element) { 680 | var $element = $(element) 681 | this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) 682 | }, this)) 683 | .end() 684 | } 685 | 686 | Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { 687 | var isOpen = $element.hasClass('in') 688 | 689 | $element.attr('aria-expanded', isOpen) 690 | $trigger 691 | .toggleClass('collapsed', !isOpen) 692 | .attr('aria-expanded', isOpen) 693 | } 694 | 695 | function getTargetFromTrigger($trigger) { 696 | var href 697 | var target = $trigger.attr('data-target') 698 | || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 699 | 700 | return $(target) 701 | } 702 | 703 | 704 | // COLLAPSE PLUGIN DEFINITION 705 | // ========================== 706 | 707 | function Plugin(option) { 708 | return this.each(function () { 709 | var $this = $(this) 710 | var data = $this.data('bs.collapse') 711 | var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) 712 | 713 | if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false 714 | if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) 715 | if (typeof option == 'string') data[option]() 716 | }) 717 | } 718 | 719 | var old = $.fn.collapse 720 | 721 | $.fn.collapse = Plugin 722 | $.fn.collapse.Constructor = Collapse 723 | 724 | 725 | // COLLAPSE NO CONFLICT 726 | // ==================== 727 | 728 | $.fn.collapse.noConflict = function () { 729 | $.fn.collapse = old 730 | return this 731 | } 732 | 733 | 734 | // COLLAPSE DATA-API 735 | // ================= 736 | 737 | $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { 738 | var $this = $(this) 739 | 740 | if (!$this.attr('data-target')) e.preventDefault() 741 | 742 | var $target = getTargetFromTrigger($this) 743 | var data = $target.data('bs.collapse') 744 | var option = data ? 'toggle' : $this.data() 745 | 746 | Plugin.call($target, option) 747 | }) 748 | 749 | }(jQuery); 750 | 751 | /* ======================================================================== 752 | * Bootstrap: dropdown.js v3.3.7 753 | * http://getbootstrap.com/javascript/#dropdowns 754 | * ======================================================================== 755 | * Copyright 2011-2016 Twitter, Inc. 756 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 757 | * ======================================================================== */ 758 | 759 | 760 | +function ($) { 761 | 'use strict'; 762 | 763 | // DROPDOWN CLASS DEFINITION 764 | // ========================= 765 | 766 | var backdrop = '.dropdown-backdrop' 767 | var toggle = '[data-toggle="dropdown"]' 768 | var Dropdown = function (element) { 769 | $(element).on('click.bs.dropdown', this.toggle) 770 | } 771 | 772 | Dropdown.VERSION = '3.3.7' 773 | 774 | function getParent($this) { 775 | var selector = $this.attr('data-target') 776 | 777 | if (!selector) { 778 | selector = $this.attr('href') 779 | selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 780 | } 781 | 782 | var $parent = selector && $(selector) 783 | 784 | return $parent && $parent.length ? $parent : $this.parent() 785 | } 786 | 787 | function clearMenus(e) { 788 | if (e && e.which === 3) return 789 | $(backdrop).remove() 790 | $(toggle).each(function () { 791 | var $this = $(this) 792 | var $parent = getParent($this) 793 | var relatedTarget = { relatedTarget: this } 794 | 795 | if (!$parent.hasClass('open')) return 796 | 797 | if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return 798 | 799 | $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) 800 | 801 | if (e.isDefaultPrevented()) return 802 | 803 | $this.attr('aria-expanded', 'false') 804 | $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) 805 | }) 806 | } 807 | 808 | Dropdown.prototype.toggle = function (e) { 809 | var $this = $(this) 810 | 811 | if ($this.is('.disabled, :disabled')) return 812 | 813 | var $parent = getParent($this) 814 | var isActive = $parent.hasClass('open') 815 | 816 | clearMenus() 817 | 818 | if (!isActive) { 819 | if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { 820 | // if mobile we use a backdrop because click events don't delegate 821 | $(document.createElement('div')) 822 | .addClass('dropdown-backdrop') 823 | .insertAfter($(this)) 824 | .on('click', clearMenus) 825 | } 826 | 827 | var relatedTarget = { relatedTarget: this } 828 | $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) 829 | 830 | if (e.isDefaultPrevented()) return 831 | 832 | $this 833 | .trigger('focus') 834 | .attr('aria-expanded', 'true') 835 | 836 | $parent 837 | .toggleClass('open') 838 | .trigger($.Event('shown.bs.dropdown', relatedTarget)) 839 | } 840 | 841 | return false 842 | } 843 | 844 | Dropdown.prototype.keydown = function (e) { 845 | if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return 846 | 847 | var $this = $(this) 848 | 849 | e.preventDefault() 850 | e.stopPropagation() 851 | 852 | if ($this.is('.disabled, :disabled')) return 853 | 854 | var $parent = getParent($this) 855 | var isActive = $parent.hasClass('open') 856 | 857 | if (!isActive && e.which != 27 || isActive && e.which == 27) { 858 | if (e.which == 27) $parent.find(toggle).trigger('focus') 859 | return $this.trigger('click') 860 | } 861 | 862 | var desc = ' li:not(.disabled):visible a' 863 | var $items = $parent.find('.dropdown-menu' + desc) 864 | 865 | if (!$items.length) return 866 | 867 | var index = $items.index(e.target) 868 | 869 | if (e.which == 38 && index > 0) index-- // up 870 | if (e.which == 40 && index < $items.length - 1) index++ // down 871 | if (!~index) index = 0 872 | 873 | $items.eq(index).trigger('focus') 874 | } 875 | 876 | 877 | // DROPDOWN PLUGIN DEFINITION 878 | // ========================== 879 | 880 | function Plugin(option) { 881 | return this.each(function () { 882 | var $this = $(this) 883 | var data = $this.data('bs.dropdown') 884 | 885 | if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) 886 | if (typeof option == 'string') data[option].call($this) 887 | }) 888 | } 889 | 890 | var old = $.fn.dropdown 891 | 892 | $.fn.dropdown = Plugin 893 | $.fn.dropdown.Constructor = Dropdown 894 | 895 | 896 | // DROPDOWN NO CONFLICT 897 | // ==================== 898 | 899 | $.fn.dropdown.noConflict = function () { 900 | $.fn.dropdown = old 901 | return this 902 | } 903 | 904 | 905 | // APPLY TO STANDARD DROPDOWN ELEMENTS 906 | // =================================== 907 | 908 | $(document) 909 | .on('click.bs.dropdown.data-api', clearMenus) 910 | .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) 911 | .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) 912 | .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) 913 | .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) 914 | 915 | }(jQuery); 916 | 917 | /* ======================================================================== 918 | * Bootstrap: modal.js v3.3.7 919 | * http://getbootstrap.com/javascript/#modals 920 | * ======================================================================== 921 | * Copyright 2011-2016 Twitter, Inc. 922 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 923 | * ======================================================================== */ 924 | 925 | 926 | +function ($) { 927 | 'use strict'; 928 | 929 | // MODAL CLASS DEFINITION 930 | // ====================== 931 | 932 | var Modal = function (element, options) { 933 | this.options = options 934 | this.$body = $(document.body) 935 | this.$element = $(element) 936 | this.$dialog = this.$element.find('.modal-dialog') 937 | this.$backdrop = null 938 | this.isShown = null 939 | this.originalBodyPad = null 940 | this.scrollbarWidth = 0 941 | this.ignoreBackdropClick = false 942 | 943 | if (this.options.remote) { 944 | this.$element 945 | .find('.modal-content') 946 | .load(this.options.remote, $.proxy(function () { 947 | this.$element.trigger('loaded.bs.modal') 948 | }, this)) 949 | } 950 | } 951 | 952 | Modal.VERSION = '3.3.7' 953 | 954 | Modal.TRANSITION_DURATION = 300 955 | Modal.BACKDROP_TRANSITION_DURATION = 150 956 | 957 | Modal.DEFAULTS = { 958 | backdrop: true, 959 | keyboard: true, 960 | show: true 961 | } 962 | 963 | Modal.prototype.toggle = function (_relatedTarget) { 964 | return this.isShown ? this.hide() : this.show(_relatedTarget) 965 | } 966 | 967 | Modal.prototype.show = function (_relatedTarget) { 968 | var that = this 969 | var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) 970 | 971 | this.$element.trigger(e) 972 | 973 | if (this.isShown || e.isDefaultPrevented()) return 974 | 975 | this.isShown = true 976 | 977 | this.checkScrollbar() 978 | this.setScrollbar() 979 | this.$body.addClass('modal-open') 980 | 981 | this.escape() 982 | this.resize() 983 | 984 | this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) 985 | 986 | this.$dialog.on('mousedown.dismiss.bs.modal', function () { 987 | that.$element.one('mouseup.dismiss.bs.modal', function (e) { 988 | if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true 989 | }) 990 | }) 991 | 992 | this.backdrop(function () { 993 | var transition = $.support.transition && that.$element.hasClass('fade') 994 | 995 | if (!that.$element.parent().length) { 996 | that.$element.appendTo(that.$body) // don't move modals dom position 997 | } 998 | 999 | that.$element 1000 | .show() 1001 | .scrollTop(0) 1002 | 1003 | that.adjustDialog() 1004 | 1005 | if (transition) { 1006 | that.$element[0].offsetWidth // force reflow 1007 | } 1008 | 1009 | that.$element.addClass('in') 1010 | 1011 | that.enforceFocus() 1012 | 1013 | var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) 1014 | 1015 | transition ? 1016 | that.$dialog // wait for modal to slide in 1017 | .one('bsTransitionEnd', function () { 1018 | that.$element.trigger('focus').trigger(e) 1019 | }) 1020 | .emulateTransitionEnd(Modal.TRANSITION_DURATION) : 1021 | that.$element.trigger('focus').trigger(e) 1022 | }) 1023 | } 1024 | 1025 | Modal.prototype.hide = function (e) { 1026 | if (e) e.preventDefault() 1027 | 1028 | e = $.Event('hide.bs.modal') 1029 | 1030 | this.$element.trigger(e) 1031 | 1032 | if (!this.isShown || e.isDefaultPrevented()) return 1033 | 1034 | this.isShown = false 1035 | 1036 | this.escape() 1037 | this.resize() 1038 | 1039 | $(document).off('focusin.bs.modal') 1040 | 1041 | this.$element 1042 | .removeClass('in') 1043 | .off('click.dismiss.bs.modal') 1044 | .off('mouseup.dismiss.bs.modal') 1045 | 1046 | this.$dialog.off('mousedown.dismiss.bs.modal') 1047 | 1048 | $.support.transition && this.$element.hasClass('fade') ? 1049 | this.$element 1050 | .one('bsTransitionEnd', $.proxy(this.hideModal, this)) 1051 | .emulateTransitionEnd(Modal.TRANSITION_DURATION) : 1052 | this.hideModal() 1053 | } 1054 | 1055 | Modal.prototype.enforceFocus = function () { 1056 | $(document) 1057 | .off('focusin.bs.modal') // guard against infinite focus loop 1058 | .on('focusin.bs.modal', $.proxy(function (e) { 1059 | if (document !== e.target && 1060 | this.$element[0] !== e.target && 1061 | !this.$element.has(e.target).length) { 1062 | this.$element.trigger('focus') 1063 | } 1064 | }, this)) 1065 | } 1066 | 1067 | Modal.prototype.escape = function () { 1068 | if (this.isShown && this.options.keyboard) { 1069 | this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { 1070 | e.which == 27 && this.hide() 1071 | }, this)) 1072 | } else if (!this.isShown) { 1073 | this.$element.off('keydown.dismiss.bs.modal') 1074 | } 1075 | } 1076 | 1077 | Modal.prototype.resize = function () { 1078 | if (this.isShown) { 1079 | $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) 1080 | } else { 1081 | $(window).off('resize.bs.modal') 1082 | } 1083 | } 1084 | 1085 | Modal.prototype.hideModal = function () { 1086 | var that = this 1087 | this.$element.hide() 1088 | this.backdrop(function () { 1089 | that.$body.removeClass('modal-open') 1090 | that.resetAdjustments() 1091 | that.resetScrollbar() 1092 | that.$element.trigger('hidden.bs.modal') 1093 | }) 1094 | } 1095 | 1096 | Modal.prototype.removeBackdrop = function () { 1097 | this.$backdrop && this.$backdrop.remove() 1098 | this.$backdrop = null 1099 | } 1100 | 1101 | Modal.prototype.backdrop = function (callback) { 1102 | var that = this 1103 | var animate = this.$element.hasClass('fade') ? 'fade' : '' 1104 | 1105 | if (this.isShown && this.options.backdrop) { 1106 | var doAnimate = $.support.transition && animate 1107 | 1108 | this.$backdrop = $(document.createElement('div')) 1109 | .addClass('modal-backdrop ' + animate) 1110 | .appendTo(this.$body) 1111 | 1112 | this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { 1113 | if (this.ignoreBackdropClick) { 1114 | this.ignoreBackdropClick = false 1115 | return 1116 | } 1117 | if (e.target !== e.currentTarget) return 1118 | this.options.backdrop == 'static' 1119 | ? this.$element[0].focus() 1120 | : this.hide() 1121 | }, this)) 1122 | 1123 | if (doAnimate) this.$backdrop[0].offsetWidth // force reflow 1124 | 1125 | this.$backdrop.addClass('in') 1126 | 1127 | if (!callback) return 1128 | 1129 | doAnimate ? 1130 | this.$backdrop 1131 | .one('bsTransitionEnd', callback) 1132 | .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : 1133 | callback() 1134 | 1135 | } else if (!this.isShown && this.$backdrop) { 1136 | this.$backdrop.removeClass('in') 1137 | 1138 | var callbackRemove = function () { 1139 | that.removeBackdrop() 1140 | callback && callback() 1141 | } 1142 | $.support.transition && this.$element.hasClass('fade') ? 1143 | this.$backdrop 1144 | .one('bsTransitionEnd', callbackRemove) 1145 | .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : 1146 | callbackRemove() 1147 | 1148 | } else if (callback) { 1149 | callback() 1150 | } 1151 | } 1152 | 1153 | // these following methods are used to handle overflowing modals 1154 | 1155 | Modal.prototype.handleUpdate = function () { 1156 | this.adjustDialog() 1157 | } 1158 | 1159 | Modal.prototype.adjustDialog = function () { 1160 | var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight 1161 | 1162 | this.$element.css({ 1163 | paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', 1164 | paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' 1165 | }) 1166 | } 1167 | 1168 | Modal.prototype.resetAdjustments = function () { 1169 | this.$element.css({ 1170 | paddingLeft: '', 1171 | paddingRight: '' 1172 | }) 1173 | } 1174 | 1175 | Modal.prototype.checkScrollbar = function () { 1176 | var fullWindowWidth = window.innerWidth 1177 | if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 1178 | var documentElementRect = document.documentElement.getBoundingClientRect() 1179 | fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) 1180 | } 1181 | this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth 1182 | this.scrollbarWidth = this.measureScrollbar() 1183 | } 1184 | 1185 | Modal.prototype.setScrollbar = function () { 1186 | var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) 1187 | this.originalBodyPad = document.body.style.paddingRight || '' 1188 | if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) 1189 | } 1190 | 1191 | Modal.prototype.resetScrollbar = function () { 1192 | this.$body.css('padding-right', this.originalBodyPad) 1193 | } 1194 | 1195 | Modal.prototype.measureScrollbar = function () { // thx walsh 1196 | var scrollDiv = document.createElement('div') 1197 | scrollDiv.className = 'modal-scrollbar-measure' 1198 | this.$body.append(scrollDiv) 1199 | var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth 1200 | this.$body[0].removeChild(scrollDiv) 1201 | return scrollbarWidth 1202 | } 1203 | 1204 | 1205 | // MODAL PLUGIN DEFINITION 1206 | // ======================= 1207 | 1208 | function Plugin(option, _relatedTarget) { 1209 | return this.each(function () { 1210 | var $this = $(this) 1211 | var data = $this.data('bs.modal') 1212 | var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) 1213 | 1214 | if (!data) $this.data('bs.modal', (data = new Modal(this, options))) 1215 | if (typeof option == 'string') data[option](_relatedTarget) 1216 | else if (options.show) data.show(_relatedTarget) 1217 | }) 1218 | } 1219 | 1220 | var old = $.fn.modal 1221 | 1222 | $.fn.modal = Plugin 1223 | $.fn.modal.Constructor = Modal 1224 | 1225 | 1226 | // MODAL NO CONFLICT 1227 | // ================= 1228 | 1229 | $.fn.modal.noConflict = function () { 1230 | $.fn.modal = old 1231 | return this 1232 | } 1233 | 1234 | 1235 | // MODAL DATA-API 1236 | // ============== 1237 | 1238 | $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { 1239 | var $this = $(this) 1240 | var href = $this.attr('href') 1241 | var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 1242 | var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) 1243 | 1244 | if ($this.is('a')) e.preventDefault() 1245 | 1246 | $target.one('show.bs.modal', function (showEvent) { 1247 | if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown 1248 | $target.one('hidden.bs.modal', function () { 1249 | $this.is(':visible') && $this.trigger('focus') 1250 | }) 1251 | }) 1252 | Plugin.call($target, option, this) 1253 | }) 1254 | 1255 | }(jQuery); 1256 | 1257 | /* ======================================================================== 1258 | * Bootstrap: tooltip.js v3.3.7 1259 | * http://getbootstrap.com/javascript/#tooltip 1260 | * Inspired by the original jQuery.tipsy by Jason Frame 1261 | * ======================================================================== 1262 | * Copyright 2011-2016 Twitter, Inc. 1263 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1264 | * ======================================================================== */ 1265 | 1266 | 1267 | +function ($) { 1268 | 'use strict'; 1269 | 1270 | // TOOLTIP PUBLIC CLASS DEFINITION 1271 | // =============================== 1272 | 1273 | var Tooltip = function (element, options) { 1274 | this.type = null 1275 | this.options = null 1276 | this.enabled = null 1277 | this.timeout = null 1278 | this.hoverState = null 1279 | this.$element = null 1280 | this.inState = null 1281 | 1282 | this.init('tooltip', element, options) 1283 | } 1284 | 1285 | Tooltip.VERSION = '3.3.7' 1286 | 1287 | Tooltip.TRANSITION_DURATION = 150 1288 | 1289 | Tooltip.DEFAULTS = { 1290 | animation: true, 1291 | placement: 'top', 1292 | selector: false, 1293 | template: '', 1294 | trigger: 'hover focus', 1295 | title: '', 1296 | delay: 0, 1297 | html: false, 1298 | container: false, 1299 | viewport: { 1300 | selector: 'body', 1301 | padding: 0 1302 | } 1303 | } 1304 | 1305 | Tooltip.prototype.init = function (type, element, options) { 1306 | this.enabled = true 1307 | this.type = type 1308 | this.$element = $(element) 1309 | this.options = this.getOptions(options) 1310 | this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) 1311 | this.inState = { click: false, hover: false, focus: false } 1312 | 1313 | if (this.$element[0] instanceof document.constructor && !this.options.selector) { 1314 | throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') 1315 | } 1316 | 1317 | var triggers = this.options.trigger.split(' ') 1318 | 1319 | for (var i = triggers.length; i--;) { 1320 | var trigger = triggers[i] 1321 | 1322 | if (trigger == 'click') { 1323 | this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) 1324 | } else if (trigger != 'manual') { 1325 | var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' 1326 | var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' 1327 | 1328 | this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) 1329 | this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) 1330 | } 1331 | } 1332 | 1333 | this.options.selector ? 1334 | (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : 1335 | this.fixTitle() 1336 | } 1337 | 1338 | Tooltip.prototype.getDefaults = function () { 1339 | return Tooltip.DEFAULTS 1340 | } 1341 | 1342 | Tooltip.prototype.getOptions = function (options) { 1343 | options = $.extend({}, this.getDefaults(), this.$element.data(), options) 1344 | 1345 | if (options.delay && typeof options.delay == 'number') { 1346 | options.delay = { 1347 | show: options.delay, 1348 | hide: options.delay 1349 | } 1350 | } 1351 | 1352 | return options 1353 | } 1354 | 1355 | Tooltip.prototype.getDelegateOptions = function () { 1356 | var options = {} 1357 | var defaults = this.getDefaults() 1358 | 1359 | this._options && $.each(this._options, function (key, value) { 1360 | if (defaults[key] != value) options[key] = value 1361 | }) 1362 | 1363 | return options 1364 | } 1365 | 1366 | Tooltip.prototype.enter = function (obj) { 1367 | var self = obj instanceof this.constructor ? 1368 | obj : $(obj.currentTarget).data('bs.' + this.type) 1369 | 1370 | if (!self) { 1371 | self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) 1372 | $(obj.currentTarget).data('bs.' + this.type, self) 1373 | } 1374 | 1375 | if (obj instanceof $.Event) { 1376 | self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true 1377 | } 1378 | 1379 | if (self.tip().hasClass('in') || self.hoverState == 'in') { 1380 | self.hoverState = 'in' 1381 | return 1382 | } 1383 | 1384 | clearTimeout(self.timeout) 1385 | 1386 | self.hoverState = 'in' 1387 | 1388 | if (!self.options.delay || !self.options.delay.show) return self.show() 1389 | 1390 | self.timeout = setTimeout(function () { 1391 | if (self.hoverState == 'in') self.show() 1392 | }, self.options.delay.show) 1393 | } 1394 | 1395 | Tooltip.prototype.isInStateTrue = function () { 1396 | for (var key in this.inState) { 1397 | if (this.inState[key]) return true 1398 | } 1399 | 1400 | return false 1401 | } 1402 | 1403 | Tooltip.prototype.leave = function (obj) { 1404 | var self = obj instanceof this.constructor ? 1405 | obj : $(obj.currentTarget).data('bs.' + this.type) 1406 | 1407 | if (!self) { 1408 | self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) 1409 | $(obj.currentTarget).data('bs.' + this.type, self) 1410 | } 1411 | 1412 | if (obj instanceof $.Event) { 1413 | self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false 1414 | } 1415 | 1416 | if (self.isInStateTrue()) return 1417 | 1418 | clearTimeout(self.timeout) 1419 | 1420 | self.hoverState = 'out' 1421 | 1422 | if (!self.options.delay || !self.options.delay.hide) return self.hide() 1423 | 1424 | self.timeout = setTimeout(function () { 1425 | if (self.hoverState == 'out') self.hide() 1426 | }, self.options.delay.hide) 1427 | } 1428 | 1429 | Tooltip.prototype.show = function () { 1430 | var e = $.Event('show.bs.' + this.type) 1431 | 1432 | if (this.hasContent() && this.enabled) { 1433 | this.$element.trigger(e) 1434 | 1435 | var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) 1436 | if (e.isDefaultPrevented() || !inDom) return 1437 | var that = this 1438 | 1439 | var $tip = this.tip() 1440 | 1441 | var tipId = this.getUID(this.type) 1442 | 1443 | this.setContent() 1444 | $tip.attr('id', tipId) 1445 | this.$element.attr('aria-describedby', tipId) 1446 | 1447 | if (this.options.animation) $tip.addClass('fade') 1448 | 1449 | var placement = typeof this.options.placement == 'function' ? 1450 | this.options.placement.call(this, $tip[0], this.$element[0]) : 1451 | this.options.placement 1452 | 1453 | var autoToken = /\s?auto?\s?/i 1454 | var autoPlace = autoToken.test(placement) 1455 | if (autoPlace) placement = placement.replace(autoToken, '') || 'top' 1456 | 1457 | $tip 1458 | .detach() 1459 | .css({ top: 0, left: 0, display: 'block' }) 1460 | .addClass(placement) 1461 | .data('bs.' + this.type, this) 1462 | 1463 | this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) 1464 | this.$element.trigger('inserted.bs.' + this.type) 1465 | 1466 | var pos = this.getPosition() 1467 | var actualWidth = $tip[0].offsetWidth 1468 | var actualHeight = $tip[0].offsetHeight 1469 | 1470 | if (autoPlace) { 1471 | var orgPlacement = placement 1472 | var viewportDim = this.getPosition(this.$viewport) 1473 | 1474 | placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : 1475 | placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : 1476 | placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : 1477 | placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : 1478 | placement 1479 | 1480 | $tip 1481 | .removeClass(orgPlacement) 1482 | .addClass(placement) 1483 | } 1484 | 1485 | var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) 1486 | 1487 | this.applyPlacement(calculatedOffset, placement) 1488 | 1489 | var complete = function () { 1490 | var prevHoverState = that.hoverState 1491 | that.$element.trigger('shown.bs.' + that.type) 1492 | that.hoverState = null 1493 | 1494 | if (prevHoverState == 'out') that.leave(that) 1495 | } 1496 | 1497 | $.support.transition && this.$tip.hasClass('fade') ? 1498 | $tip 1499 | .one('bsTransitionEnd', complete) 1500 | .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : 1501 | complete() 1502 | } 1503 | } 1504 | 1505 | Tooltip.prototype.applyPlacement = function (offset, placement) { 1506 | var $tip = this.tip() 1507 | var width = $tip[0].offsetWidth 1508 | var height = $tip[0].offsetHeight 1509 | 1510 | // manually read margins because getBoundingClientRect includes difference 1511 | var marginTop = parseInt($tip.css('margin-top'), 10) 1512 | var marginLeft = parseInt($tip.css('margin-left'), 10) 1513 | 1514 | // we must check for NaN for ie 8/9 1515 | if (isNaN(marginTop)) marginTop = 0 1516 | if (isNaN(marginLeft)) marginLeft = 0 1517 | 1518 | offset.top += marginTop 1519 | offset.left += marginLeft 1520 | 1521 | // $.fn.offset doesn't round pixel values 1522 | // so we use setOffset directly with our own function B-0 1523 | $.offset.setOffset($tip[0], $.extend({ 1524 | using: function (props) { 1525 | $tip.css({ 1526 | top: Math.round(props.top), 1527 | left: Math.round(props.left) 1528 | }) 1529 | } 1530 | }, offset), 0) 1531 | 1532 | $tip.addClass('in') 1533 | 1534 | // check to see if placing tip in new offset caused the tip to resize itself 1535 | var actualWidth = $tip[0].offsetWidth 1536 | var actualHeight = $tip[0].offsetHeight 1537 | 1538 | if (placement == 'top' && actualHeight != height) { 1539 | offset.top = offset.top + height - actualHeight 1540 | } 1541 | 1542 | var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) 1543 | 1544 | if (delta.left) offset.left += delta.left 1545 | else offset.top += delta.top 1546 | 1547 | var isVertical = /top|bottom/.test(placement) 1548 | var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight 1549 | var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' 1550 | 1551 | $tip.offset(offset) 1552 | this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) 1553 | } 1554 | 1555 | Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { 1556 | this.arrow() 1557 | .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') 1558 | .css(isVertical ? 'top' : 'left', '') 1559 | } 1560 | 1561 | Tooltip.prototype.setContent = function () { 1562 | var $tip = this.tip() 1563 | var title = this.getTitle() 1564 | 1565 | $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) 1566 | $tip.removeClass('fade in top bottom left right') 1567 | } 1568 | 1569 | Tooltip.prototype.hide = function (callback) { 1570 | var that = this 1571 | var $tip = $(this.$tip) 1572 | var e = $.Event('hide.bs.' + this.type) 1573 | 1574 | function complete() { 1575 | if (that.hoverState != 'in') $tip.detach() 1576 | if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary. 1577 | that.$element 1578 | .removeAttr('aria-describedby') 1579 | .trigger('hidden.bs.' + that.type) 1580 | } 1581 | callback && callback() 1582 | } 1583 | 1584 | this.$element.trigger(e) 1585 | 1586 | if (e.isDefaultPrevented()) return 1587 | 1588 | $tip.removeClass('in') 1589 | 1590 | $.support.transition && $tip.hasClass('fade') ? 1591 | $tip 1592 | .one('bsTransitionEnd', complete) 1593 | .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : 1594 | complete() 1595 | 1596 | this.hoverState = null 1597 | 1598 | return this 1599 | } 1600 | 1601 | Tooltip.prototype.fixTitle = function () { 1602 | var $e = this.$element 1603 | if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { 1604 | $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') 1605 | } 1606 | } 1607 | 1608 | Tooltip.prototype.hasContent = function () { 1609 | return this.getTitle() 1610 | } 1611 | 1612 | Tooltip.prototype.getPosition = function ($element) { 1613 | $element = $element || this.$element 1614 | 1615 | var el = $element[0] 1616 | var isBody = el.tagName == 'BODY' 1617 | 1618 | var elRect = el.getBoundingClientRect() 1619 | if (elRect.width == null) { 1620 | // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 1621 | elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) 1622 | } 1623 | var isSvg = window.SVGElement && el instanceof window.SVGElement 1624 | // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3. 1625 | // See https://github.com/twbs/bootstrap/issues/20280 1626 | var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset()) 1627 | var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } 1628 | var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null 1629 | 1630 | return $.extend({}, elRect, scroll, outerDims, elOffset) 1631 | } 1632 | 1633 | Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { 1634 | return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : 1635 | placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : 1636 | placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : 1637 | /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } 1638 | 1639 | } 1640 | 1641 | Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { 1642 | var delta = { top: 0, left: 0 } 1643 | if (!this.$viewport) return delta 1644 | 1645 | var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 1646 | var viewportDimensions = this.getPosition(this.$viewport) 1647 | 1648 | if (/right|left/.test(placement)) { 1649 | var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll 1650 | var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight 1651 | if (topEdgeOffset < viewportDimensions.top) { // top overflow 1652 | delta.top = viewportDimensions.top - topEdgeOffset 1653 | } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow 1654 | delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset 1655 | } 1656 | } else { 1657 | var leftEdgeOffset = pos.left - viewportPadding 1658 | var rightEdgeOffset = pos.left + viewportPadding + actualWidth 1659 | if (leftEdgeOffset < viewportDimensions.left) { // left overflow 1660 | delta.left = viewportDimensions.left - leftEdgeOffset 1661 | } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow 1662 | delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset 1663 | } 1664 | } 1665 | 1666 | return delta 1667 | } 1668 | 1669 | Tooltip.prototype.getTitle = function () { 1670 | var title 1671 | var $e = this.$element 1672 | var o = this.options 1673 | 1674 | title = $e.attr('data-original-title') 1675 | || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) 1676 | 1677 | return title 1678 | } 1679 | 1680 | Tooltip.prototype.getUID = function (prefix) { 1681 | do prefix += ~~(Math.random() * 1000000) 1682 | while (document.getElementById(prefix)) 1683 | return prefix 1684 | } 1685 | 1686 | Tooltip.prototype.tip = function () { 1687 | if (!this.$tip) { 1688 | this.$tip = $(this.options.template) 1689 | if (this.$tip.length != 1) { 1690 | throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') 1691 | } 1692 | } 1693 | return this.$tip 1694 | } 1695 | 1696 | Tooltip.prototype.arrow = function () { 1697 | return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) 1698 | } 1699 | 1700 | Tooltip.prototype.enable = function () { 1701 | this.enabled = true 1702 | } 1703 | 1704 | Tooltip.prototype.disable = function () { 1705 | this.enabled = false 1706 | } 1707 | 1708 | Tooltip.prototype.toggleEnabled = function () { 1709 | this.enabled = !this.enabled 1710 | } 1711 | 1712 | Tooltip.prototype.toggle = function (e) { 1713 | var self = this 1714 | if (e) { 1715 | self = $(e.currentTarget).data('bs.' + this.type) 1716 | if (!self) { 1717 | self = new this.constructor(e.currentTarget, this.getDelegateOptions()) 1718 | $(e.currentTarget).data('bs.' + this.type, self) 1719 | } 1720 | } 1721 | 1722 | if (e) { 1723 | self.inState.click = !self.inState.click 1724 | if (self.isInStateTrue()) self.enter(self) 1725 | else self.leave(self) 1726 | } else { 1727 | self.tip().hasClass('in') ? self.leave(self) : self.enter(self) 1728 | } 1729 | } 1730 | 1731 | Tooltip.prototype.destroy = function () { 1732 | var that = this 1733 | clearTimeout(this.timeout) 1734 | this.hide(function () { 1735 | that.$element.off('.' + that.type).removeData('bs.' + that.type) 1736 | if (that.$tip) { 1737 | that.$tip.detach() 1738 | } 1739 | that.$tip = null 1740 | that.$arrow = null 1741 | that.$viewport = null 1742 | that.$element = null 1743 | }) 1744 | } 1745 | 1746 | 1747 | // TOOLTIP PLUGIN DEFINITION 1748 | // ========================= 1749 | 1750 | function Plugin(option) { 1751 | return this.each(function () { 1752 | var $this = $(this) 1753 | var data = $this.data('bs.tooltip') 1754 | var options = typeof option == 'object' && option 1755 | 1756 | if (!data && /destroy|hide/.test(option)) return 1757 | if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) 1758 | if (typeof option == 'string') data[option]() 1759 | }) 1760 | } 1761 | 1762 | var old = $.fn.tooltip 1763 | 1764 | $.fn.tooltip = Plugin 1765 | $.fn.tooltip.Constructor = Tooltip 1766 | 1767 | 1768 | // TOOLTIP NO CONFLICT 1769 | // =================== 1770 | 1771 | $.fn.tooltip.noConflict = function () { 1772 | $.fn.tooltip = old 1773 | return this 1774 | } 1775 | 1776 | }(jQuery); 1777 | 1778 | /* ======================================================================== 1779 | * Bootstrap: popover.js v3.3.7 1780 | * http://getbootstrap.com/javascript/#popovers 1781 | * ======================================================================== 1782 | * Copyright 2011-2016 Twitter, Inc. 1783 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1784 | * ======================================================================== */ 1785 | 1786 | 1787 | +function ($) { 1788 | 'use strict'; 1789 | 1790 | // POPOVER PUBLIC CLASS DEFINITION 1791 | // =============================== 1792 | 1793 | var Popover = function (element, options) { 1794 | this.init('popover', element, options) 1795 | } 1796 | 1797 | if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') 1798 | 1799 | Popover.VERSION = '3.3.7' 1800 | 1801 | Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { 1802 | placement: 'right', 1803 | trigger: 'click', 1804 | content: '', 1805 | template: '' 1806 | }) 1807 | 1808 | 1809 | // NOTE: POPOVER EXTENDS tooltip.js 1810 | // ================================ 1811 | 1812 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) 1813 | 1814 | Popover.prototype.constructor = Popover 1815 | 1816 | Popover.prototype.getDefaults = function () { 1817 | return Popover.DEFAULTS 1818 | } 1819 | 1820 | Popover.prototype.setContent = function () { 1821 | var $tip = this.tip() 1822 | var title = this.getTitle() 1823 | var content = this.getContent() 1824 | 1825 | $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) 1826 | $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events 1827 | this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' 1828 | ](content) 1829 | 1830 | $tip.removeClass('fade top bottom left right in') 1831 | 1832 | // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do 1833 | // this manually by checking the contents. 1834 | if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() 1835 | } 1836 | 1837 | Popover.prototype.hasContent = function () { 1838 | return this.getTitle() || this.getContent() 1839 | } 1840 | 1841 | Popover.prototype.getContent = function () { 1842 | var $e = this.$element 1843 | var o = this.options 1844 | 1845 | return $e.attr('data-content') 1846 | || (typeof o.content == 'function' ? 1847 | o.content.call($e[0]) : 1848 | o.content) 1849 | } 1850 | 1851 | Popover.prototype.arrow = function () { 1852 | return (this.$arrow = this.$arrow || this.tip().find('.arrow')) 1853 | } 1854 | 1855 | 1856 | // POPOVER PLUGIN DEFINITION 1857 | // ========================= 1858 | 1859 | function Plugin(option) { 1860 | return this.each(function () { 1861 | var $this = $(this) 1862 | var data = $this.data('bs.popover') 1863 | var options = typeof option == 'object' && option 1864 | 1865 | if (!data && /destroy|hide/.test(option)) return 1866 | if (!data) $this.data('bs.popover', (data = new Popover(this, options))) 1867 | if (typeof option == 'string') data[option]() 1868 | }) 1869 | } 1870 | 1871 | var old = $.fn.popover 1872 | 1873 | $.fn.popover = Plugin 1874 | $.fn.popover.Constructor = Popover 1875 | 1876 | 1877 | // POPOVER NO CONFLICT 1878 | // =================== 1879 | 1880 | $.fn.popover.noConflict = function () { 1881 | $.fn.popover = old 1882 | return this 1883 | } 1884 | 1885 | }(jQuery); 1886 | 1887 | /* ======================================================================== 1888 | * Bootstrap: scrollspy.js v3.3.7 1889 | * http://getbootstrap.com/javascript/#scrollspy 1890 | * ======================================================================== 1891 | * Copyright 2011-2016 Twitter, Inc. 1892 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1893 | * ======================================================================== */ 1894 | 1895 | 1896 | +function ($) { 1897 | 'use strict'; 1898 | 1899 | // SCROLLSPY CLASS DEFINITION 1900 | // ========================== 1901 | 1902 | function ScrollSpy(element, options) { 1903 | this.$body = $(document.body) 1904 | this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) 1905 | this.options = $.extend({}, ScrollSpy.DEFAULTS, options) 1906 | this.selector = (this.options.target || '') + ' .nav li > a' 1907 | this.offsets = [] 1908 | this.targets = [] 1909 | this.activeTarget = null 1910 | this.scrollHeight = 0 1911 | 1912 | this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) 1913 | this.refresh() 1914 | this.process() 1915 | } 1916 | 1917 | ScrollSpy.VERSION = '3.3.7' 1918 | 1919 | ScrollSpy.DEFAULTS = { 1920 | offset: 10 1921 | } 1922 | 1923 | ScrollSpy.prototype.getScrollHeight = function () { 1924 | return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) 1925 | } 1926 | 1927 | ScrollSpy.prototype.refresh = function () { 1928 | var that = this 1929 | var offsetMethod = 'offset' 1930 | var offsetBase = 0 1931 | 1932 | this.offsets = [] 1933 | this.targets = [] 1934 | this.scrollHeight = this.getScrollHeight() 1935 | 1936 | if (!$.isWindow(this.$scrollElement[0])) { 1937 | offsetMethod = 'position' 1938 | offsetBase = this.$scrollElement.scrollTop() 1939 | } 1940 | 1941 | this.$body 1942 | .find(this.selector) 1943 | .map(function () { 1944 | var $el = $(this) 1945 | var href = $el.data('target') || $el.attr('href') 1946 | var $href = /^#./.test(href) && $(href) 1947 | 1948 | return ($href 1949 | && $href.length 1950 | && $href.is(':visible') 1951 | && [[$href[offsetMethod]().top + offsetBase, href]]) || null 1952 | }) 1953 | .sort(function (a, b) { return a[0] - b[0] }) 1954 | .each(function () { 1955 | that.offsets.push(this[0]) 1956 | that.targets.push(this[1]) 1957 | }) 1958 | } 1959 | 1960 | ScrollSpy.prototype.process = function () { 1961 | var scrollTop = this.$scrollElement.scrollTop() + this.options.offset 1962 | var scrollHeight = this.getScrollHeight() 1963 | var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() 1964 | var offsets = this.offsets 1965 | var targets = this.targets 1966 | var activeTarget = this.activeTarget 1967 | var i 1968 | 1969 | if (this.scrollHeight != scrollHeight) { 1970 | this.refresh() 1971 | } 1972 | 1973 | if (scrollTop >= maxScroll) { 1974 | return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) 1975 | } 1976 | 1977 | if (activeTarget && scrollTop < offsets[0]) { 1978 | this.activeTarget = null 1979 | return this.clear() 1980 | } 1981 | 1982 | for (i = offsets.length; i--;) { 1983 | activeTarget != targets[i] 1984 | && scrollTop >= offsets[i] 1985 | && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) 1986 | && this.activate(targets[i]) 1987 | } 1988 | } 1989 | 1990 | ScrollSpy.prototype.activate = function (target) { 1991 | this.activeTarget = target 1992 | 1993 | this.clear() 1994 | 1995 | var selector = this.selector + 1996 | '[data-target="' + target + '"],' + 1997 | this.selector + '[href="' + target + '"]' 1998 | 1999 | var active = $(selector) 2000 | .parents('li') 2001 | .addClass('active') 2002 | 2003 | if (active.parent('.dropdown-menu').length) { 2004 | active = active 2005 | .closest('li.dropdown') 2006 | .addClass('active') 2007 | } 2008 | 2009 | active.trigger('activate.bs.scrollspy') 2010 | } 2011 | 2012 | ScrollSpy.prototype.clear = function () { 2013 | $(this.selector) 2014 | .parentsUntil(this.options.target, '.active') 2015 | .removeClass('active') 2016 | } 2017 | 2018 | 2019 | // SCROLLSPY PLUGIN DEFINITION 2020 | // =========================== 2021 | 2022 | function Plugin(option) { 2023 | return this.each(function () { 2024 | var $this = $(this) 2025 | var data = $this.data('bs.scrollspy') 2026 | var options = typeof option == 'object' && option 2027 | 2028 | if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) 2029 | if (typeof option == 'string') data[option]() 2030 | }) 2031 | } 2032 | 2033 | var old = $.fn.scrollspy 2034 | 2035 | $.fn.scrollspy = Plugin 2036 | $.fn.scrollspy.Constructor = ScrollSpy 2037 | 2038 | 2039 | // SCROLLSPY NO CONFLICT 2040 | // ===================== 2041 | 2042 | $.fn.scrollspy.noConflict = function () { 2043 | $.fn.scrollspy = old 2044 | return this 2045 | } 2046 | 2047 | 2048 | // SCROLLSPY DATA-API 2049 | // ================== 2050 | 2051 | $(window).on('load.bs.scrollspy.data-api', function () { 2052 | $('[data-spy="scroll"]').each(function () { 2053 | var $spy = $(this) 2054 | Plugin.call($spy, $spy.data()) 2055 | }) 2056 | }) 2057 | 2058 | }(jQuery); 2059 | 2060 | /* ======================================================================== 2061 | * Bootstrap: tab.js v3.3.7 2062 | * http://getbootstrap.com/javascript/#tabs 2063 | * ======================================================================== 2064 | * Copyright 2011-2016 Twitter, Inc. 2065 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 2066 | * ======================================================================== */ 2067 | 2068 | 2069 | +function ($) { 2070 | 'use strict'; 2071 | 2072 | // TAB CLASS DEFINITION 2073 | // ==================== 2074 | 2075 | var Tab = function (element) { 2076 | // jscs:disable requireDollarBeforejQueryAssignment 2077 | this.element = $(element) 2078 | // jscs:enable requireDollarBeforejQueryAssignment 2079 | } 2080 | 2081 | Tab.VERSION = '3.3.7' 2082 | 2083 | Tab.TRANSITION_DURATION = 150 2084 | 2085 | Tab.prototype.show = function () { 2086 | var $this = this.element 2087 | var $ul = $this.closest('ul:not(.dropdown-menu)') 2088 | var selector = $this.data('target') 2089 | 2090 | if (!selector) { 2091 | selector = $this.attr('href') 2092 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 2093 | } 2094 | 2095 | if ($this.parent('li').hasClass('active')) return 2096 | 2097 | var $previous = $ul.find('.active:last a') 2098 | var hideEvent = $.Event('hide.bs.tab', { 2099 | relatedTarget: $this[0] 2100 | }) 2101 | var showEvent = $.Event('show.bs.tab', { 2102 | relatedTarget: $previous[0] 2103 | }) 2104 | 2105 | $previous.trigger(hideEvent) 2106 | $this.trigger(showEvent) 2107 | 2108 | if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return 2109 | 2110 | var $target = $(selector) 2111 | 2112 | this.activate($this.closest('li'), $ul) 2113 | this.activate($target, $target.parent(), function () { 2114 | $previous.trigger({ 2115 | type: 'hidden.bs.tab', 2116 | relatedTarget: $this[0] 2117 | }) 2118 | $this.trigger({ 2119 | type: 'shown.bs.tab', 2120 | relatedTarget: $previous[0] 2121 | }) 2122 | }) 2123 | } 2124 | 2125 | Tab.prototype.activate = function (element, container, callback) { 2126 | var $active = container.find('> .active') 2127 | var transition = callback 2128 | && $.support.transition 2129 | && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) 2130 | 2131 | function next() { 2132 | $active 2133 | .removeClass('active') 2134 | .find('> .dropdown-menu > .active') 2135 | .removeClass('active') 2136 | .end() 2137 | .find('[data-toggle="tab"]') 2138 | .attr('aria-expanded', false) 2139 | 2140 | element 2141 | .addClass('active') 2142 | .find('[data-toggle="tab"]') 2143 | .attr('aria-expanded', true) 2144 | 2145 | if (transition) { 2146 | element[0].offsetWidth // reflow for transition 2147 | element.addClass('in') 2148 | } else { 2149 | element.removeClass('fade') 2150 | } 2151 | 2152 | if (element.parent('.dropdown-menu').length) { 2153 | element 2154 | .closest('li.dropdown') 2155 | .addClass('active') 2156 | .end() 2157 | .find('[data-toggle="tab"]') 2158 | .attr('aria-expanded', true) 2159 | } 2160 | 2161 | callback && callback() 2162 | } 2163 | 2164 | $active.length && transition ? 2165 | $active 2166 | .one('bsTransitionEnd', next) 2167 | .emulateTransitionEnd(Tab.TRANSITION_DURATION) : 2168 | next() 2169 | 2170 | $active.removeClass('in') 2171 | } 2172 | 2173 | 2174 | // TAB PLUGIN DEFINITION 2175 | // ===================== 2176 | 2177 | function Plugin(option) { 2178 | return this.each(function () { 2179 | var $this = $(this) 2180 | var data = $this.data('bs.tab') 2181 | 2182 | if (!data) $this.data('bs.tab', (data = new Tab(this))) 2183 | if (typeof option == 'string') data[option]() 2184 | }) 2185 | } 2186 | 2187 | var old = $.fn.tab 2188 | 2189 | $.fn.tab = Plugin 2190 | $.fn.tab.Constructor = Tab 2191 | 2192 | 2193 | // TAB NO CONFLICT 2194 | // =============== 2195 | 2196 | $.fn.tab.noConflict = function () { 2197 | $.fn.tab = old 2198 | return this 2199 | } 2200 | 2201 | 2202 | // TAB DATA-API 2203 | // ============ 2204 | 2205 | var clickHandler = function (e) { 2206 | e.preventDefault() 2207 | Plugin.call($(this), 'show') 2208 | } 2209 | 2210 | $(document) 2211 | .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) 2212 | .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) 2213 | 2214 | }(jQuery); 2215 | 2216 | /* ======================================================================== 2217 | * Bootstrap: affix.js v3.3.7 2218 | * http://getbootstrap.com/javascript/#affix 2219 | * ======================================================================== 2220 | * Copyright 2011-2016 Twitter, Inc. 2221 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 2222 | * ======================================================================== */ 2223 | 2224 | 2225 | +function ($) { 2226 | 'use strict'; 2227 | 2228 | // AFFIX CLASS DEFINITION 2229 | // ====================== 2230 | 2231 | var Affix = function (element, options) { 2232 | this.options = $.extend({}, Affix.DEFAULTS, options) 2233 | 2234 | this.$target = $(this.options.target) 2235 | .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) 2236 | .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) 2237 | 2238 | this.$element = $(element) 2239 | this.affixed = null 2240 | this.unpin = null 2241 | this.pinnedOffset = null 2242 | 2243 | this.checkPosition() 2244 | } 2245 | 2246 | Affix.VERSION = '3.3.7' 2247 | 2248 | Affix.RESET = 'affix affix-top affix-bottom' 2249 | 2250 | Affix.DEFAULTS = { 2251 | offset: 0, 2252 | target: window 2253 | } 2254 | 2255 | Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { 2256 | var scrollTop = this.$target.scrollTop() 2257 | var position = this.$element.offset() 2258 | var targetHeight = this.$target.height() 2259 | 2260 | if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false 2261 | 2262 | if (this.affixed == 'bottom') { 2263 | if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' 2264 | return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' 2265 | } 2266 | 2267 | var initializing = this.affixed == null 2268 | var colliderTop = initializing ? scrollTop : position.top 2269 | var colliderHeight = initializing ? targetHeight : height 2270 | 2271 | if (offsetTop != null && scrollTop <= offsetTop) return 'top' 2272 | if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' 2273 | 2274 | return false 2275 | } 2276 | 2277 | Affix.prototype.getPinnedOffset = function () { 2278 | if (this.pinnedOffset) return this.pinnedOffset 2279 | this.$element.removeClass(Affix.RESET).addClass('affix') 2280 | var scrollTop = this.$target.scrollTop() 2281 | var position = this.$element.offset() 2282 | return (this.pinnedOffset = position.top - scrollTop) 2283 | } 2284 | 2285 | Affix.prototype.checkPositionWithEventLoop = function () { 2286 | setTimeout($.proxy(this.checkPosition, this), 1) 2287 | } 2288 | 2289 | Affix.prototype.checkPosition = function () { 2290 | if (!this.$element.is(':visible')) return 2291 | 2292 | var height = this.$element.height() 2293 | var offset = this.options.offset 2294 | var offsetTop = offset.top 2295 | var offsetBottom = offset.bottom 2296 | var scrollHeight = Math.max($(document).height(), $(document.body).height()) 2297 | 2298 | if (typeof offset != 'object') offsetBottom = offsetTop = offset 2299 | if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) 2300 | if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) 2301 | 2302 | var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) 2303 | 2304 | if (this.affixed != affix) { 2305 | if (this.unpin != null) this.$element.css('top', '') 2306 | 2307 | var affixType = 'affix' + (affix ? '-' + affix : '') 2308 | var e = $.Event(affixType + '.bs.affix') 2309 | 2310 | this.$element.trigger(e) 2311 | 2312 | if (e.isDefaultPrevented()) return 2313 | 2314 | this.affixed = affix 2315 | this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null 2316 | 2317 | this.$element 2318 | .removeClass(Affix.RESET) 2319 | .addClass(affixType) 2320 | .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') 2321 | } 2322 | 2323 | if (affix == 'bottom') { 2324 | this.$element.offset({ 2325 | top: scrollHeight - height - offsetBottom 2326 | }) 2327 | } 2328 | } 2329 | 2330 | 2331 | // AFFIX PLUGIN DEFINITION 2332 | // ======================= 2333 | 2334 | function Plugin(option) { 2335 | return this.each(function () { 2336 | var $this = $(this) 2337 | var data = $this.data('bs.affix') 2338 | var options = typeof option == 'object' && option 2339 | 2340 | if (!data) $this.data('bs.affix', (data = new Affix(this, options))) 2341 | if (typeof option == 'string') data[option]() 2342 | }) 2343 | } 2344 | 2345 | var old = $.fn.affix 2346 | 2347 | $.fn.affix = Plugin 2348 | $.fn.affix.Constructor = Affix 2349 | 2350 | 2351 | // AFFIX NO CONFLICT 2352 | // ================= 2353 | 2354 | $.fn.affix.noConflict = function () { 2355 | $.fn.affix = old 2356 | return this 2357 | } 2358 | 2359 | 2360 | // AFFIX DATA-API 2361 | // ============== 2362 | 2363 | $(window).on('load', function () { 2364 | $('[data-spy="affix"]').each(function () { 2365 | var $spy = $(this) 2366 | var data = $spy.data() 2367 | 2368 | data.offset = data.offset || {} 2369 | 2370 | if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom 2371 | if (data.offsetTop != null) data.offset.top = data.offsetTop 2372 | 2373 | Plugin.call($spy, data) 2374 | }) 2375 | }) 2376 | 2377 | }(jQuery); 2378 | -------------------------------------------------------------------------------- /static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /static/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manumathewthomas/ImageDenoisingGAN/4f1484e36542e960df3d97f24e35132098277ae7/static/output.png -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: crimson; 3 | } -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Denoising GAN 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 50 | 51 | 52 |
53 | 54 | 55 |
56 |
57 |

Denoising GAN 58 | 59 |

60 |
61 |
62 | 63 | 64 | 65 |
66 |
67 |

Input

68 | 69 | 70 | 71 |
72 | 73 | 74 |
75 | 76 |
77 | 78 |
79 |

Output

80 | 81 | 82 | 83 |
84 |
85 |
86 |
87 | 88 | 89 | 90 |
91 |
92 | 93 | 94 | 95 | 96 | 97 |
98 | 99 | 100 | 101 |
102 | 103 | 104 |
105 |
106 |
107 | 108 |
109 |
110 | 111 |
112 | 113 |
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import tensorflow as tf 4 | import numpy as np 5 | 6 | from utils import * 7 | from model import * 8 | 9 | from skimage import measure 10 | 11 | 12 | 13 | def test(image): 14 | tf.reset_default_graph() 15 | 16 | global_step = tf.Variable(0, dtype=tf.int32, trainable=False, name='global_step') 17 | 18 | gen_in = tf.placeholder(shape=[None, BATCH_SHAPE[1], BATCH_SHAPE[2], BATCH_SHAPE[3]], dtype=tf.float32, name='generated_image') 19 | real_in = tf.placeholder(shape=[None, BATCH_SHAPE[1], BATCH_SHAPE[2], BATCH_SHAPE[3]], dtype=tf.float32, name='groundtruth_image') 20 | 21 | Gz = generator(gen_in) 22 | 23 | 24 | 25 | init = tf.global_variables_initializer() 26 | with tf.Session() as sess: 27 | sess.run(init) 28 | 29 | saver = initialize(sess) 30 | initial_step = global_step.eval() 31 | 32 | start_time = time.time() 33 | n_batches = 200 34 | total_iteration = n_batches * N_EPOCHS 35 | 36 | image = sess.run(tf.map_fn(lambda img: tf.image.per_image_standardization(img), image)) 37 | image = sess.run(Gz, feed_dict={gen_in: image}) 38 | image = np.resize(image[0][56:, :, :], [144, 256, 3]) 39 | imsave('output', image) 40 | return image 41 | 42 | def denoise(image): 43 | image = scipy.misc.imread(image, mode='RGB').astype('float32') 44 | npad = ((56, 56), (0, 0), (0, 0)) 45 | image = np.pad(image, pad_width=npad, mode='constant', constant_values=0) 46 | image = np.expand_dims(image, axis=0) 47 | print(image[0].shape) 48 | output = test(image) 49 | return output 50 | 51 | 52 | 53 | if __name__=='__main__': 54 | image = scipy.misc.imread(sys.argv[-1], mode='RGB').astype('float32') 55 | npad = ((56, 56), (0, 0), (0, 0)) 56 | image = np.pad(image, pad_width=npad, mode='constant', constant_values=0) 57 | image = np.expand_dims(image, axis=0) 58 | print(image[0].shape) 59 | test(image) 60 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import tensorflow as tf 4 | import numpy as np 5 | 6 | from utils import * 7 | from model import * 8 | 9 | from skimage import measure 10 | 11 | 12 | 13 | def train(): 14 | tf.reset_default_graph() 15 | 16 | global_step = tf.Variable(0, dtype=tf.int32, trainable=False, name='global_step') 17 | 18 | gen_in = tf.placeholder(shape=[None, BATCH_SHAPE[1], BATCH_SHAPE[2], BATCH_SHAPE[3]], dtype=tf.float32, name='generated_image') 19 | real_in = tf.placeholder(shape=[None, BATCH_SHAPE[1], BATCH_SHAPE[2], BATCH_SHAPE[3]], dtype=tf.float32, name='groundtruth_image') 20 | 21 | Gz = generator(gen_in) 22 | Dx = discriminator(real_in) 23 | Dg = discriminator(Gz, reuse=True) 24 | 25 | real_in_bgr = tf.map_fn(lambda img: RGB_TO_BGR(img), real_in) 26 | Gz_bgr = tf.map_fn(lambda img: RGB_TO_BGR(img), Gz) 27 | 28 | psnr=0 29 | ssim=0 30 | 31 | d_loss = -tf.reduce_mean(tf.log(Dx) + tf.log(1.-Dg)) 32 | g_loss = ADVERSARIAL_LOSS_FACTOR * -tf.reduce_mean(tf.log(Dg)) + PIXEL_LOSS_FACTOR * get_pixel_loss(real_in, Gz) \ 33 | + STYLE_LOSS_FACTOR * get_style_loss(real_in_bgr, Gz_bgr) + SMOOTH_LOSS_FACTOR * get_smooth_loss(Gz) 34 | 35 | t_vars = tf.trainable_variables() 36 | d_vars = [var for var in t_vars if 'd_' in var.name] 37 | g_vars = [var for var in t_vars if 'g_' in var.name] 38 | 39 | d_solver = tf.train.AdamOptimizer(LEARNING_RATE).minimize(d_loss, var_list=d_vars, global_step=global_step) 40 | g_solver = tf.train.AdamOptimizer(LEARNING_RATE).minimize(g_loss, var_list=g_vars) 41 | 42 | 43 | init = tf.global_variables_initializer() 44 | with tf.Session() as sess: 45 | sess.run(init) 46 | 47 | saver = initialize(sess) 48 | initial_step = global_step.eval() 49 | 50 | start_time = time.time() 51 | n_batches = 200 52 | total_iteration = n_batches * N_EPOCHS 53 | 54 | validation_batch = sess.run(tf.map_fn(lambda img: tf.image.per_image_standardization(img), validation)) 55 | 56 | 57 | for index in range(initial_step, total_iteration): 58 | input_batch = load_next_training_batch() 59 | training_batch, groundtruth_batch = np.split(input_batch, 2, axis=2) 60 | 61 | training_batch = sess.run(tf.map_fn(lambda img: tf.image.per_image_standardization(img), training_batch)) 62 | groundtruth_batch = sess.run(tf.map_fn(lambda img: tf.image.per_image_standardization(img), groundtruth_batch)) 63 | 64 | 65 | _, d_loss_cur = sess.run([d_solver, d_loss], feed_dict={gen_in: training_batch, real_in: groundtruth_batch}) 66 | _, g_loss_cur = sess.run([g_solver, g_loss], feed_dict={gen_in: training_batch, real_in: groundtruth_batch}) 67 | 68 | 69 | 70 | 71 | if(index + 1) % SKIP_STEP == 0: 72 | 73 | saver.save(sess, CKPT_DIR, index) 74 | image = sess.run(Gz, feed_dict={gen_in: validation_batch}) 75 | image = np.resize(image[7][56:, :, :], [144, 256, 3]) 76 | 77 | imsave('val_%d' % (index+1), image) 78 | image = scipy.misc.imread(IMG_DIR+'val_%d.png' % (index+1), mode='RGB').astype('float32') 79 | psnr = measure.compare_psnr(metrics_image, image, data_range=255) 80 | ssim = measure.compare_ssim(metrics_image, image, multichannel=True, data_range=255, win_size=11) 81 | 82 | print( 83 | "Step {}/{} Gen Loss: ".format(index + 1, total_iteration) + str(g_loss_cur) + " Disc Loss: " + str( 84 | d_loss_cur)+ " PSNR: "+str(psnr)+" SSIM: "+str(ssim)) 85 | 86 | 87 | 88 | if __name__=='__main__': 89 | training_dir_list = training_dataset_init() 90 | validation = load_validation() 91 | train() 92 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | import glob 5 | import scipy.misc 6 | from itertools import cycle 7 | 8 | import numpy as np 9 | import tensorflow as tf 10 | 11 | 12 | from libs import vgg16 13 | 14 | from PIL import Image 15 | 16 | 17 | LEARNING_RATE = 0.002 18 | BATCH_SIZE = 5 19 | BATCH_SHAPE = [BATCH_SIZE, 256, 256, 3] 20 | SKIP_STEP = 10 21 | N_EPOCHS = 500 22 | CKPT_DIR = './Checkpoints/' 23 | IMG_DIR = './Images/' 24 | GRAPH_DIR = './Graphs/' 25 | TRAINING_SET_DIR= './dataset/training/' 26 | # GROUNDTRUTH_SET_DIR='./dataset/groundtruth/' 27 | VALIDATION_SET_DIR='./dataset/validation/' 28 | METRICS_SET_DIR='./dataset/metrics/' 29 | TRAINING_DIR_LIST = [] 30 | ADVERSARIAL_LOSS_FACTOR = 0.5 31 | PIXEL_LOSS_FACTOR = 1.0 32 | STYLE_LOSS_FACTOR = 1.0 33 | SMOOTH_LOSS_FACTOR = 1.0 34 | metrics_image = scipy.misc.imread(METRICS_SET_DIR+'gt.png', mode='RGB').astype('float32') 35 | 36 | 37 | def initialize(sess): 38 | saver = tf.train.Saver() 39 | writer = tf.summary.FileWriter(GRAPH_DIR, sess.graph) 40 | 41 | if not os.path.exists(CKPT_DIR): 42 | os.makedirs(CKPT_DIR) 43 | if not os.path.exists(IMG_DIR): 44 | os.makedirs(IMG_DIR) 45 | 46 | ckpt = tf.train.get_checkpoint_state(os.path.dirname(CKPT_DIR)) 47 | if ckpt and ckpt.model_checkpoint_path: 48 | saver.restore(sess, ckpt.model_checkpoint_path) 49 | return saver 50 | 51 | def get_training_dir_list(): 52 | training_list = [d[1] for d in os.walk(TRAINING_SET_DIR)] 53 | global TRAINING_DIR_LIST 54 | TRAINING_DIR_LIST = training_list[0] 55 | return TRAINING_DIR_LIST 56 | 57 | def load_next_training_batch(): 58 | batch = next(pool) 59 | 60 | # filelist = sorted(glob.glob(TRAINING_SET_DIR+ batch +'/*.png'), key=alphanum_key) 61 | # batch = np.array([np.array(scipy.misc.imread(fname, mode='RGB').astype('float32')) for fname in filelist]) 62 | # npad =((0, 0), (56, 56), (0, 0), (0, 0)) 63 | # batch = np.pad(batch, pad_width=npad, mode='constant', constant_values=0) 64 | return batch 65 | 66 | # def load_groundtruth(): 67 | # filelist = sorted(glob.glob(GROUNDTRUTH_SET_DIR + '/*.png'), key=alphanum_key) 68 | # groundtruth = np.array([np.array(scipy.misc.imread(fname, mode='RGB').astype('float32')) for fname in filelist]) 69 | # # npad = ((0, 0), (56, 56), (0, 0), (0, 0)) 70 | # # groundtruth = np.pad(groundtruth, pad_width=npad, mode='constant', constant_values=0) 71 | # return groundtruth 72 | 73 | def load_validation(): 74 | filelist = sorted(glob.glob(VALIDATION_SET_DIR + '/*.png'), key=alphanum_key) 75 | validation = np.array([np.array(scipy.misc.imread(fname, mode='RGB').astype('float32')) for fname in filelist]) 76 | npad = ((0, 0), (56, 56), (0, 0), (0, 0)) 77 | validation = np.pad(validation, pad_width=npad, mode='constant', constant_values=0) 78 | return validation 79 | 80 | def training_dataset_init(): 81 | filelist = sorted(glob.glob(TRAINING_SET_DIR + '/*.png'), key=alphanum_key) 82 | batch = np.array([np.array(scipy.misc.imread(fname, mode='RGB').astype('float32')) for fname in filelist]) 83 | batch = split(batch, BATCH_SIZE) 84 | training_dir_list = get_training_dir_list() 85 | global pool 86 | pool = cycle(batch) 87 | # return training_dir_list 88 | 89 | 90 | def imsave(filename, image): 91 | scipy.misc.imsave(IMG_DIR+filename+'.png', image) 92 | 93 | def merge_images(file1, file2): 94 | """Merge two images into one, displayed side by side 95 | :param file1: path to first image file 96 | :param file2: path to second image file 97 | :return: the merged Image object 98 | """ 99 | image1 = Image.fromarray(np.uint8(file1)) 100 | image2 = Image.fromarray(np.uint8(file2)) 101 | 102 | (width1, height1) = image1.size 103 | (width2, height2) = image2.size 104 | 105 | result_width = width1 + width2 106 | result_height = max(height1, height2) 107 | 108 | result = Image.new('RGB', (result_width, result_height)) 109 | result.paste(im=image1, box=(0, 0)) 110 | result.paste(im=image2, box=(width1, 0)) 111 | return result 112 | 113 | 114 | def tryint(s): 115 | try: 116 | return int(s) 117 | except: 118 | return s 119 | 120 | def alphanum_key(s): 121 | """ Turn a string into a list of string and number chunks. 122 | "z23a" -> ["z", 23, "a"] 123 | """ 124 | return [ tryint(c) for c in re.split('([0-9]+)', s) ] 125 | 126 | 127 | def split(arr, size): 128 | arrs = [] 129 | while len(arr) > size: 130 | pice = arr[:size] 131 | arrs.append(pice) 132 | arr = arr[size:] 133 | arrs.append(arr) 134 | return arrs 135 | 136 | 137 | def lrelu(x, leak=0.2, name='lrelu'): 138 | with tf.variable_scope(name): 139 | f1 = 0.5 * (1 + leak) 140 | f2 = 0.5 * (1 - leak) 141 | return f1 * x + f2 * abs(x) 142 | 143 | def RGB_TO_BGR(img): 144 | img_channel_swap = img[..., ::-1] 145 | # img_channel_swap_1 = tf.reverse(img, axis=[-1]) 146 | return img_channel_swap 147 | 148 | 149 | def get_pixel_loss(target,prediction): 150 | pixel_difference = target - prediction 151 | pixel_loss = tf.nn.l2_loss(pixel_difference) 152 | return pixel_loss 153 | 154 | def get_style_layer_vgg16(image): 155 | net = vgg16.get_vgg_model() 156 | style_layer = 'conv2_2/conv2_2:0' 157 | feature_transformed_image = tf.import_graph_def( 158 | net['graph_def'], 159 | name='vgg', 160 | input_map={'images:0': image},return_elements=[style_layer]) 161 | feature_transformed_image = (feature_transformed_image[0]) 162 | return feature_transformed_image 163 | 164 | def get_style_loss(target,prediction): 165 | feature_transformed_target = get_style_layer_vgg16(target) 166 | feature_transformed_prediction = get_style_layer_vgg16(prediction) 167 | feature_count = tf.shape(feature_transformed_target)[3] 168 | style_loss = tf.reduce_sum(tf.square(feature_transformed_target-feature_transformed_prediction)) 169 | style_loss = style_loss/tf.cast(feature_count, tf.float32) 170 | return style_loss 171 | 172 | def get_smooth_loss(image): 173 | batch_count = tf.shape(image)[0] 174 | image_height = tf.shape(image)[1] 175 | image_width = tf.shape(image)[2] 176 | 177 | horizontal_normal = tf.slice(image, [0, 0, 0,0], [batch_count, image_height, image_width-1,3]) 178 | horizontal_one_right = tf.slice(image, [0, 0, 1,0], [batch_count, image_height, image_width-1,3]) 179 | vertical_normal = tf.slice(image, [0, 0, 0,0], [batch_count, image_height-1, image_width,3]) 180 | vertical_one_right = tf.slice(image, [0, 1, 0,0], [batch_count, image_height-1, image_width,3]) 181 | smooth_loss = tf.nn.l2_loss(horizontal_normal-horizontal_one_right)+tf.nn.l2_loss(vertical_normal-vertical_one_right) 182 | return smooth_loss 183 | 184 | --------------------------------------------------------------------------------