├── auto_download.py ├── image_download_script.js ├── accuracy_plotter.py ├── cropping_coordinates.py ├── README.md ├── dataset.py └── landslide_detection_main.py /auto_download.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Download example.""" 3 | 4 | import ee 5 | import ee.mapclient 6 | 7 | start = ee.Date('2017-01-01') 8 | finish = ee.Date('2017-03-20') 9 | 10 | rectangle = ee.Geometry.Polygon( 11 | [[-76.6,1.1],[-76.7,1.1],[-76.6,1.2],[-76.7,1.2]]) 12 | 13 | 14 | ee.Initialize() 15 | ee.mapclient.centerMap(-76.6399, 1.1519, 14) 16 | collection = ee.ImageCollection('COPERNICUS/S2') 17 | 18 | filteredCollection = collection.filterBounds(rectangle).filterDate(start,finish) 19 | 20 | first = filteredCollection.first() 21 | 22 | # Get a download URL for an image. 23 | path = first.getDownloadURL({ 24 | 'region': '[[-76.6,1.1],[-76.7,1.1],[-76.6,1.2],[-76.7,1.2]]', 25 | 'scale': 10 26 | }) 27 | print(path) 28 | -------------------------------------------------------------------------------- /image_download_script.js: -------------------------------------------------------------------------------- 1 | 2 | var startDate = '2017-02-16'; //YYYY-MM-DD 3 | var finishDate = '2017-06-15'; 4 | 5 | 6 | var region = '[[-120.8913,47.5137], [-120.7899,47.5137], [-120.8913,47.4451], [-120.7899,47.4451]]' 7 | var rectangle = [-120.8913,47.4451,-120.7899,47.5137]; 8 | 9 | var rectangle1 = ee.Geometry.Rectangle(rectangle); 10 | 11 | var dataset = ee.ImageCollection("COPERNICUS/S2").filterBounds(rectangle1) 12 | .filterDate(startDate, finishDate) 13 | .sort('system:time_start', true); 14 | 15 | var selectors = ["B2","B3","B4","B8","B12","QA60"] 16 | 17 | var mean_cloud = function(image){ 18 | return(image.select("QA60").reduceRegion({ 19 | reducer: ee.Reducer.mean(), 20 | geometry: rectangle1, 21 | scale: 10}).get('QA60')) 22 | }; 23 | 24 | 25 | //select only the useful bands 26 | //dataset = dataset.select(selectors) 27 | //var data = dataset.toList(dataset.size()); 28 | //print(ee.Image(data.get(0))); 29 | //for (var i=0; i<100; i++){ 30 | // var image = ee.Image(data.get(i)); 31 | // print(mean_cloud(image),image.get("MGRS_TILE")); 32 | // image = image.select(["B2","B3","B4","B8","B12"]); 33 | // print(image.getDownloadURL( 34 | // {'region': region, 35 | // 'scale': 10})); 36 | // } 37 | -------------------------------------------------------------------------------- /accuracy_plotter.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | 3 | source = 'Results/' 4 | 5 | numbs = ['0-3','4-7','8-11','12-15','16-19'] 6 | 7 | train_accurs = {} 8 | validation_accurs = {} 9 | 10 | for numb in numbs: 11 | file = open('Results/validation_accurs_'+numb+'.txt', 'r') 12 | for line in file: 13 | try: 14 | epochs = int(float(line[:-1])) 15 | except(ValueError): 16 | if line.find('Train')!=-1: 17 | accur = float(line.split(':')[-1]) 18 | try: 19 | train_accurs[epochs].append(accur) 20 | except(KeyError): 21 | train_accurs[epochs] = [accur] 22 | 23 | elif line.find('Eval')!=-1: 24 | accur = float(line.split(':')[-1]) 25 | try: 26 | validation_accurs[epochs].append(accur) 27 | except(KeyError): 28 | validation_accurs[epochs] = [accur] 29 | 30 | xs = [] 31 | train_ys = [] 32 | val_ys = [] 33 | for key in train_accurs: 34 | print(key, sum(train_accurs[key])/len(train_accurs[key])) 35 | xs.append(key) 36 | train_ys.append(sum(train_accurs[key])/len(train_accurs[key])) 37 | 38 | for key in validation_accurs: 39 | print(key, sum(validation_accurs[key])/len(validation_accurs[key])) 40 | val_ys.append(sum(validation_accurs[key])/len(validation_accurs[key])) 41 | 42 | plt.plot(xs, train_ys, label='Traning set') #, title = 'Validation accuracy as a function of epochs' 43 | plt.plot(xs, val_ys, label='Validation set') 44 | plt.legend() 45 | plt.ylabel('Balanced accuracy') 46 | plt.xlabel('Epochs trained') 47 | plt.show() 48 | -------------------------------------------------------------------------------- /cropping_coordinates.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | # Distances are measured in kilometers. 4 | # Longitudes and latitudes are measured in degrees. 5 | # Earth is assumed to be perfectly spherical. 6 | 7 | earth_radius = 6271.0 8 | degrees_to_radians = math.pi/180.0 9 | radians_to_degrees = 180.0/math.pi 10 | 11 | def change_in_latitude(kms): 12 | "Given a distance north, return the change in latitude." 13 | return (kms/earth_radius)*radians_to_degrees 14 | 15 | def change_in_longitude(latitude, kms): 16 | "Given a latitude and a distance west, return the change in longitude." 17 | # Find the radius of a circle around the earth at given latitude. 18 | r = earth_radius*math.cos(latitude*degrees_to_radians) 19 | return (kms/r)*radians_to_degrees 20 | 21 | def ten_km_square(latitude, longitude): 22 | slat, nlat = latitude+change_in_latitude(-3.75), latitude+change_in_latitude(3.75) 23 | wlon = longitude+change_in_longitude(latitude,-3.75) 24 | elon = longitude+change_in_longitude(latitude, 3.75) 25 | return(nlat, wlon, slat, elon) 26 | 27 | def main(lon, lat): 28 | '''First argument degrees longitude (E is positive, W negative) 29 | of the landslide location, 30 | second argument latitude (N positive, S negative), 31 | in decimal format(not minutes etc.)''' 32 | nlat, wlon, slat, elon = ten_km_square(lat,lon) 33 | #print("(NLat:{:.4f},WLon:{:.4f}),(SLat:{:.4f},ELon:{:.4f});".format(nlat, wlon, slat, elon)) 34 | print("var region = '[[{:.4f},{:.4f}], [{:.4f},{:.4f}], [{:.4f},{:.4f}], [{:.4f},{:.4f}]]';".format(wlon,nlat,elon,nlat,wlon,slat,elon,slat)) 35 | print("var rectangle = [{:.4f},{:.4f},{:.4f},{:.4f}];".format(wlon,slat,elon,nlat)) 36 | 37 | #change these to longitude, latitude 38 | main(18.07769,44.14354) 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Predicting Landslides using CNNs and Sentinel-2 Data 2 | 3 | ### Quick outline of files: 4 | Landslide_detection_main.py trains the network and does most of the other work, uses create_dataset.py to create a dataset out of images in a folder called 'earth_engine_good'. 5 | 6 | Accuracy_plotter.py creates a plot of accuracies during training based on the text files in results folder(copy pasted from what the python file prints). 7 | 8 | image_download_script.js can be pasted directly into the https://code.earthengine.google.com console and you just have to enter the coordinates of the earthquake into cropping_coordinates.py to get the bounding boxes. 9 | 10 | 11 | MIT License 12 | 13 | Copyright (c) [2019] [Max Langenkamp, Tuomas Oikarinen] 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | -------------------------------------------------------------------------------- /dataset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from PIL import Image 4 | 5 | def create_dataset(source_folder, eval_sessions): 6 | train_xs=[] 7 | train_ys=[] 8 | eval_xs = [] 9 | eval_ys = [] 10 | 11 | k=0 12 | for event in os.listdir(source_folder): 13 | name, month, day, year = event.split('_') 14 | date = int(year+month+day) 15 | before = [] 16 | after = [] 17 | print(event) 18 | for folder in os.listdir(source_folder+'/'+event): 19 | #print(folder) 20 | try: 21 | image_date = int(folder.split('T')[0]) 22 | except(ValueError): 23 | print("Invalid name: ", folder) 24 | continue 25 | arrays = [] 26 | for filename in os.listdir(source_folder+"/"+event+"/"+folder): 27 | #print(filename) 28 | if filename.split('.')[-1] == 'tif': 29 | img = Image.open(source_folder+"/"+event+"/"+folder+"/"+filename) 30 | arrays.append(np.expand_dims(np.array(img), axis=2)) 31 | img.close() 32 | #print(np.shape(arrays[-1])) 33 | a = np.concatenate(arrays, axis=2) 34 | extra1 = (a.shape[0]-750)//2 35 | extra2 = (a.shape[1]-750)//2 36 | a = a[extra1:750+extra1,extra2:750+extra2] 37 | if image_date > date: 38 | after.append(a) 39 | elif image_date < date: 40 | before.append(a) 41 | print(len(before), len(after)) 42 | 43 | for i in range(len(before)): 44 | if k in eval_sessions: 45 | for j in range(len(before)): 46 | if j>=i: 47 | eval_xs.append(np.concatenate([np.expand_dims(before[i],axis=0), 48 | np.expand_dims(before[j],axis=0)],axis=0)) 49 | eval_ys.append([0]) 50 | for j in range(len(after[:3])): 51 | eval_xs.append(np.concatenate([np.expand_dims(before[i],axis=0), 52 | np.expand_dims(after[j],axis=0)],axis=0)) 53 | eval_ys.append([1]) 54 | 55 | else: 56 | for j in range(len(before)): 57 | if j>=i: 58 | train_xs.append(np.concatenate([np.expand_dims(before[i],axis=0), 59 | np.expand_dims(before[j],axis=0)],axis=0)) 60 | train_ys.append([0]) 61 | for j in range(len(after[:3])): 62 | train_xs.append(np.concatenate([np.expand_dims(before[i],axis=0), 63 | np.expand_dims(after[j],axis=0)],axis=0)) 64 | train_ys.append([1]) 65 | k+=1 66 | 67 | 68 | train_xs = np.concatenate([np.expand_dims(i,axis=0) for i in train_xs], axis=0) 69 | train_ys = np.array(train_ys) 70 | eval_xs = np.concatenate([np.expand_dims(i,axis=0) for i in eval_xs], axis=0) 71 | eval_ys = np.array(eval_ys) 72 | np.save('datasets/train_xs.npy', train_xs) 73 | np.save('datasets/train_ys.npy', train_ys) 74 | np.save('datasets/eval_xs.npy', eval_xs) 75 | np.save('datasets/eval_ys.npy', eval_ys) 76 | print("Data saved!") 77 | 78 | def load(): 79 | train_xs = np.load('datasets/train_xs.npy') 80 | train_ys = np.load('datasets/train_ys.npy') 81 | eval_xs = np.load('datasets/eval_xs.npy') 82 | eval_ys = np.load('datasets/eval_ys.npy') 83 | 84 | return train_xs, train_ys, eval_xs, eval_ys 85 | -------------------------------------------------------------------------------- /landslide_detection_main.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import numpy as np 3 | from PIL import Image 4 | 5 | import dataset 6 | 7 | #creates a dataset from images in "earth_engine_good" directory using the first 4 landslides 8 | #as evaluation set, don't have to run create_dataset every time 9 | eval_sets = [i for i in range(0,4)] 10 | dataset.create_dataset("earth_engine_good", eval_sets) 11 | train_xs, train_ys, eval_xs, eval_ys = dataset.load() 12 | 13 | def print_statistics(curr_l, curr_preds, curr_y): 14 | '''Prints accuracy on each class as well as overall accuracy and balanced accuracy''' 15 | tp = 0 16 | fp = 0 17 | fn = 0 18 | tn = 0 19 | for i in range(len(curr_preds)): 20 | if curr_preds[i] == 1 and curr_y[i] == 1: 21 | tp += 1 22 | elif curr_preds[i] == 1 and curr_y[i] == 0: 23 | fp += 1 24 | elif curr_preds[i] == 0 and curr_y[i] == 1: 25 | fn += 1 26 | elif curr_preds[i] == 0 and curr_y[i] == 0: 27 | tn += 1 28 | try: 29 | prec = tp/(tp+fp) 30 | except(ZeroDivisionError): 31 | prec = 0 32 | 33 | try: 34 | recall = tp/(tp+fn) 35 | except(ZeroDivisionError): 36 | recall = 0 37 | 38 | try: 39 | f1 = 2*prec*recall/(prec+recall) 40 | except(ZeroDivisionError): 41 | f1 = 0 42 | print("Eval: Loss:{:.3f}, landslide:{}/{}, no landslide:{}/{}, accur:{:.3f}, Mean accuracy:{:.3f}".format(curr_l, 43 | tp, tp+fn, tn, fp+tn, 44 | (tp+tn)/(tp+tn+fp+fn), 45 | 0.5*(tp/(tp+fn)+tn/(fp+tn)))) 46 | 47 | def rotate_flip_batch(x, noise_factor=0): 48 | """ 49 | Randomly rotates and flips examples in given batch 50 | X is 5D array where the x and y image axes are axes 2 and 3, 51 | noise_factor is a multiplier of how much random noise we want to add to the image, 52 | using nonzero values of noise_factor significantly reduces performance 53 | 54 | Return augmented 5D array 55 | """ 56 | #high = np.amax(x) 57 | #print("High: ", high) 58 | #print(x[0,0,:,:,0]) 59 | #print(x[0,0,:,:,0]*float(256/high)) 60 | #im = Image.fromarray(x[0,0,:,:,0]*256/float(high)) 61 | #im.show() 62 | batch = x.shape[0] 63 | rotate_degree = np.random.choice([0,1,2,3]) 64 | flip_axis = np.random.choice([0,2,3]) 65 | to_select = np.random.randint(batch, size=batch//2) 66 | x[to_select] = np.rot90(x[to_select],axes=(2,3),k=rotate_degree) 67 | if noise_factor != 0: 68 | x= np.array(np.array(x,dtype=np.float16)+ noise_factor*np.random.random(x.shape),dtype=np.float16) 69 | if flip_axis != 0: 70 | #im = Image.fromarray(np.flip(x,axis=flip_axis)[0,0,:,:,0]/float(high)) 71 | #im.show() 72 | return np.flip(x,axis=flip_axis) 73 | return x 74 | 75 | 76 | print(np.shape(train_xs)) 77 | print(np.shape(train_ys)) 78 | 79 | #might want to change this if memery is an issue 80 | batch_size = len(eval_xs) 81 | 82 | x = tf.placeholder(tf.float32, [batch_size, 2, 750, 750, 5]) 83 | cropped = tf.random_crop(x, size = [batch_size, 2, 512, 512, 5]) 84 | cropped = tf.image.random_brightness(cropped, max_delta=0.3) 85 | #cropped = tf.layers.batch_normalization(cropped, training = True) 86 | 87 | conv1 = tf.layers.conv3d(cropped, filters=4, kernel_size=[3,7,7], 88 | padding='same', activation=tf.nn.relu) 89 | max_pool1 = tf.layers.max_pooling3d(conv1, pool_size=[1,2,2], strides=[1,2,2], padding='same') 90 | #max_pool1 = tf.layers.batch_normalization(max_pool1, training = True) 91 | 92 | conv2 = tf.layers.conv3d(max_pool1, filters=16, kernel_size=[3,5,5], 93 | padding='same', activation=tf.nn.relu) 94 | #conv2 = tf.layers.batch_normalization(conv2, training = True) 95 | 96 | conv22 = tf.layers.conv3d(conv2, filters=16, kernel_size=[3,5,5], 97 | padding='same', activation=tf.nn.relu) 98 | max_pool2 = tf.layers.max_pooling3d(conv22, pool_size=[1,2,2], strides=[1,2,2], padding='same') 99 | #max_pool2 = tf.layers.batch_normalization(max_pool2, training = True) 100 | 101 | conv3 = tf.layers.conv3d(max_pool2, filters=16, kernel_size=[3,5,5], 102 | padding='same', activation=tf.nn.relu) 103 | #conv3= tf.layers.batch_normalization(conv3, training = True) 104 | conv33 = tf.layers.conv3d(conv3, filters=16, kernel_size=[3,5,5], 105 | padding='same', activation=tf.nn.relu) 106 | max_pool3 = tf.layers.max_pooling3d(conv33, pool_size=[1,2,2], strides=[1,2,2], padding='same') 107 | #max_pool3 = tf.layers.batch_normalization(max_pool3, training = True) 108 | 109 | conv4 = tf.layers.conv3d(max_pool3, filters=32, kernel_size=[3,5,5], 110 | padding='same', activation=tf.nn.relu) 111 | #conv4 = tf.layers.batch_normalization(conv4, training = True) 112 | conv44 = tf.layers.conv3d(conv4, filters=32, kernel_size=[3,5,5], 113 | padding='same', activation=tf.nn.relu) 114 | max_pool4 = tf.layers.max_pooling3d(conv44, pool_size=[1,2,2], strides=[1,2,2], padding='same') 115 | #max_pool4 = tf.layers.batch_normalization(max_pool4, training = True) 116 | 117 | conv5 = tf.layers.conv3d(max_pool4, filters=64, kernel_size=[3,5,5], 118 | padding='same', activation=tf.nn.relu) 119 | max_pool5 = tf.layers.max_pooling3d(conv5, pool_size=[2,2,2], strides=[2,2,2],padding='same') 120 | #max_pool5 = tf.layers.batch_normalization(max_pool5, training = True) 121 | 122 | 123 | keep_prob = tf.placeholder(tf.float32, None) 124 | 125 | flattened = tf.layers.flatten(max_pool5) 126 | fully_connected = tf.layers.dense(flattened, units=256, activation=tf.nn.relu) 127 | dropout = tf.nn.dropout(fully_connected, keep_prob=keep_prob) 128 | #dropout = tf.layers.batch_normalization(dropout, training = True) 129 | 130 | output = tf.layers.dense(dropout, units=1, activation=None) 131 | 132 | y = tf.placeholder(tf.float32, shape=[batch_size,1]) 133 | loss = tf.losses.sigmoid_cross_entropy(y, output) 134 | 135 | preds = tf.round(tf.nn.sigmoid(output)) 136 | accuracy = tf.equal(y, preds) 137 | optimizer = tf.train.AdamOptimizer(learning_rate=1e-4) 138 | step = optimizer.minimize(loss) 139 | 140 | sess = tf.InteractiveSession() 141 | sess.run(tf.global_variables_initializer()) 142 | 143 | 144 | 145 | #trains for 121 epochs 146 | total = 0 147 | correct = 0 148 | for i in range(121*(len(train_xs)//batch_size)): 149 | s = [range(len(train_xs))] 150 | j = i%(len(train_xs)//batch_size) 151 | if j==0: 152 | np.random.shuffle(s) 153 | train_xs = train_xs[s] 154 | train_ys = train_ys[s] 155 | _, curr_l, accur = sess.run([step, loss, accuracy], feed_dict={x:rotate_flip_batch(train_xs[j*batch_size:(j+1)*batch_size]), 156 | y:train_ys[j*batch_size:(j+1)*batch_size],keep_prob:0.5}) 157 | for value in accur: 158 | total += 1 159 | if value == True: 160 | correct +=1 161 | 162 | if i%(4*(len(train_xs)//batch_size))==0: 163 | print(i/(len(train_xs)//batch_size)) 164 | print("Train: Loss:{:.3f}, Accur:{:.3f}".format(curr_l, correct/total)) 165 | total = 0 166 | correct = 0 167 | curr_l, curr_preds, curr_y = sess.run([loss, preds, y], feed_dict={x:eval_xs, y:eval_ys, 168 | keep_prob:1}) 169 | print_statistics(curr_l, curr_preds, curr_y) 170 | 171 | 172 | 173 | 174 | 175 | 176 | --------------------------------------------------------------------------------