├── .gitattributes ├── PreProcessing.py ├── README.md ├── cnn_article └── giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5 ├── datasets └── giuriati_2 │ └── 20170621_deg0_HHVV.npy ├── main.ipynb └── net.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.py linguist-language=python 2 | *.ipynb linguist-documentation 3 | -------------------------------------------------------------------------------- /PreProcessing.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python3 2 | """ 3 | @Author: Francesco Picetti - francesco.picetti@polimi.it 4 | """ 5 | import numpy as np 6 | 7 | 8 | def remove_row_mean(in_content): 9 | """ 10 | Remove the mean along the inline (x) direction 11 | """ 12 | if in_content.dtype == np.uint8: 13 | in_content = in_content / 255. 14 | 15 | return in_content - np.mean(in_content, 2)[:,:,None] 16 | 17 | 18 | 19 | def rectify_column(in_content, margin=20): 20 | """ 21 | rectify the ground interface 22 | """ 23 | temp = in_content 24 | 25 | for bsc_idx, bscan in enumerate(in_content): 26 | temp[bsc_idx] = np.roll(bscan, -np.argmax(bscan, axis=0) + margin, axis=1) 27 | 28 | return temp 29 | 30 | def cubic_dynamics(in_content, th=0.05): 31 | """ 32 | Use a cubic power for expanding the dynamics of the data 33 | :param in_content: input volume 34 | :param th: value of the input signal to be mapped to 1 (i.e. threshold of the cubic curve) 35 | :return: processed volume 36 | """ 37 | temp = in_content - np.mean(in_content, (1,2))[:,None,None] 38 | temp /= th 39 | temp = temp**3 40 | 41 | return temp 42 | 43 | def normalize_silvia(in_content, sigmaS=14.5, meanS=121.8, alpha=13): 44 | """ 45 | normalize wrt the synthetic data 46 | :param in_content: input volume 47 | :param sigmaS: standard deviation of the synthetic data 48 | :param meanS: mean of the synthetic data 49 | :param alpha: it's magic 50 | :return: processed volume 51 | """ 52 | sigmaIn = np.std(in_content, (1,2)) 53 | return meanS + sigmaS / sigmaIn[:,None,None] / alpha * (in_content - np.mean(in_content, (1,2))[:,None,None]) 54 | 55 | def normalize_silvia_hard(in_content, sigmaS=14.5, meanS=121.8, alpha=13): 56 | return (in_content - 0.3267) / 0.0084 * sigmaS / alpha + meanS 57 | 58 | def normalize(in_content, in_min=None, in_max=None): 59 | if in_min is None and in_max is None: 60 | in_min = np.min(in_content) 61 | in_max = np.max(in_content) 62 | in_content = (in_content - in_min) / (in_max - in_min) 63 | in_content = in_content*2 - 1 64 | return in_content, in_min, in_max 65 | 66 | def apply_transform(in_content, transform:callable): 67 | return transform(in_content) 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Landmine Detection Using Autoencoders on Multi-polarization GPR Volumetric Data 2 | 3 | This repository contains a running example and part of the dataset related to the paper: 4 | 5 | Paolo Bestagini, Federico Lombardi, Maurizio Lualdi, Francesco Picetti, Stefano Tubaro, *Landmine Detection Using Autoencoder on Multi-polarization GPR Volumetric Data*, May 2020, [10.1109/TGRS.2020.2984951](https://doi.org/10.1109/TGRS.2020.2984951) 6 | 7 | ### Abstract 8 | 9 | Buried landmines and unexploded remnants of war are a constant threat for the population of many countries that have been hit by wars in the past years. 10 | The huge amount of human lives lost due to this phenomenon has been a strong motivation for the research community toward the development of safe and robust techniques designed for landmine clearance. 11 | Nonetheless, being able to detect and localize buried landmines with high precision in an automatic fashion is still considered a challenging task due to the many different boundary conditions that characterize this problem (e.g., several kinds of objects to detect, different soils and meteorological conditions, etc.). 12 | In this paper, we propose a novel technique for buried object detection tailored to unexploded landmine discovery. 13 | The proposed solution exploits a specific kind of convolutional neural network (CNN) known as autoencoder to analyze volumetric data acquired with ground penetrating radar (GPR) using different polarizations. 14 | This method works in an anomaly detection framework, indeed we only train the autoencoder on GPR data acquired on landmine-free areas. 15 | The system then recognizes landmines as objects that are dissimilar to the soil used during the training step. 16 | Experiments conducted on real data show that the proposed technique requires little training and no ad-hoc data pre-processing to achieve accuracy higher than 93% on challenging datasets. 17 | -------------------------------------------------------------------------------- /cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polimi-ispl/landmine_detection_autoencoder/cb0622373f6ff6b343c4273eabd711afd8529180/cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5 -------------------------------------------------------------------------------- /datasets/giuriati_2/20170621_deg0_HHVV.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polimi-ispl/landmine_detection_autoencoder/cb0622373f6ff6b343c4273eabd711afd8529180/datasets/giuriati_2/20170621_deg0_HHVV.npy -------------------------------------------------------------------------------- /main.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This notebook demonstrates the procedure presented in\n", 8 | "\n", 9 | "P. Bestagini, F. Lombardi, M. Lualdi, F. Picetti, S. Tubaro, Landmine Detection Using Autoencoders on Multi-polarization GPR Volumetric Data, Oct. 2018" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 11, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import os\n", 19 | "import numpy as np\n", 20 | "from sklearn.utils import shuffle\n", 21 | "from sklearn.model_selection import train_test_split\n", 22 | "\n", 23 | "import PreProcessing\n", 24 | "import net\n", 25 | "from python_patch_extractor import PatchExtractor\n", 26 | "\n", 27 | "from keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "# Variables" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 12, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "in_path = 'giuriati_2/20170621_deg0_HHVV.npy'\n", 44 | "out_path = 'cnn_article'\n", 45 | "architecture = 'Auto3D2'\n", 46 | "ny = 3 # number of adjacent B-scans to be considered\n", 47 | "data_augmentation = True\n", 48 | "preprocessing = 'normalize'\n", 49 | "patch_size = 64\n", 50 | "patch_stride = 4\n", 51 | "n_bsc = 5 # number of B_scans for training" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 13, 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "def parse_pp(string):\n", 61 | " return getattr(PreProcessing, string)\n", 62 | "\n", 63 | "def parse_net(string):\n", 64 | " return getattr(net, string)" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 14, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "if not os.path.exists(out_path):\n", 74 | " os.makedirs(out_path)\n", 75 | "\n", 76 | "field, campaign = in_path.split('/')\n", 77 | "campaign, extension = campaign.split('.')" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "# Load Datasets" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 15, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "dataset = np.load('./datasets/'+str(in_path)).item()\n", 94 | "\n", 95 | "train_bsc_idx = np.where(np.asarray(dataset['ground_truth']) == 0)[0][:n_bsc]\n", 96 | "trainset = dataset['data'][train_bsc_idx]\n", 97 | "trainset = np.moveaxis(trainset, np.argmin(trainset.shape), -1)\n", 98 | "del dataset" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "### block extraction" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 16, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "if patch_size is not None:\n", 115 | " patch_size = (patch_size, patch_size)\n", 116 | "else:\n", 117 | " patch_size = trainset.shape[1:]\n", 118 | "\n", 119 | "patch_size = patch_size + (ny,)\n", 120 | "patch_stride = (patch_stride, patch_stride, 1)\n", 121 | "\n", 122 | "pe = PatchExtractor.PatchExtractor(patch_size, stride=patch_stride)\n", 123 | "\n", 124 | "train_patches = pe.extract(trainset)\n", 125 | "# reshaping\n", 126 | "train_patches = train_patches.reshape((-1,) + patch_size)\n", 127 | "\n", 128 | "# preprocessing each patch\n", 129 | "train_patches, min_tr, max_tr = PreProcessing.apply_transform(train_patches, transform=parse_pp(preprocessing))\n", 130 | "\n", 131 | "# Data augmentation (default=True)\n", 132 | "if data_augmentation:\n", 133 | " train_patches = np.concatenate([train_patches, np.flip(train_patches, axis=2).copy()], axis=0)\n", 134 | "\n", 135 | "train_patches = shuffle(train_patches)\n", 136 | "\n", 137 | "# create training and validation sets\n", 138 | "train_patches, val_patches, train_index, val_index = train_test_split(train_patches,\n", 139 | " np.arange(train_patches.shape[0]),\n", 140 | " test_size=0.5,\n", 141 | " random_state=118\n", 142 | " )" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "# CNN Architecture" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 17, 155 | "metadata": {}, 156 | "outputs": [ 157 | { 158 | "name": "stderr", 159 | "output_type": "stream", 160 | "text": [ 161 | "/opt/miniconda3/lib/python3.6/site-packages/keras/callbacks.py:999: UserWarning: `epsilon` argument is deprecated and will be removed, use `min_delta` instead.\n", 162 | " warnings.warn('`epsilon` argument is deprecated and '\n" 163 | ] 164 | } 165 | ], 166 | "source": [ 167 | "sets = net.Settings()\n", 168 | "patience = sets.patience\n", 169 | "lr_factor = sets.lr_factor\n", 170 | "batch_size = sets.batch_size\n", 171 | "epochs = sets.epochs\n", 172 | "\n", 173 | "autoencoder, encoder = parse_net(architecture)(patch_size)\n", 174 | "\n", 175 | "out_name = field+'_'+campaign+'_'+architecture+'_patch'+str(patch_size[0])+'_stride'+str(patch_stride[0])+'_bsc'+str(n_bsc)+'_ny'+str(ny)\n", 176 | "\n", 177 | "lr_chkpt = ReduceLROnPlateau(monitor='val_loss',\n", 178 | " factor=lr_factor,\n", 179 | " patience=patience//2,\n", 180 | " verbose=0,\n", 181 | " mode='auto',\n", 182 | " epsilon=0.0001,\n", 183 | " cooldown=0,\n", 184 | " min_lr=0)\n", 185 | "save_chkpt = ModelCheckpoint(os.path.join(out_path, out_name+'.h5'),\n", 186 | " monitor='val_loss',\n", 187 | " verbose=1,\n", 188 | " save_best_only=True,\n", 189 | " save_weights_only=True,\n", 190 | " mode='min')\n", 191 | "stop_chkpt = EarlyStopping(monitor='val_loss',\n", 192 | " patience=patience)" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 18, 198 | "metadata": {}, 199 | "outputs": [ 200 | { 201 | "name": "stdout", 202 | "output_type": "stream", 203 | "text": [ 204 | "_________________________________________________________________\n", 205 | "Layer (type) Output Shape Param # \n", 206 | "=================================================================\n", 207 | "g_in_0 (InputLayer) (None, 64, 64, 3) 0 \n", 208 | "_________________________________________________________________\n", 209 | "g_conv_0 (Conv2D) (None, 64, 64, 16) 1744 \n", 210 | "_________________________________________________________________\n", 211 | "g_conv_1 (Conv2D) (None, 32, 32, 16) 6416 \n", 212 | "_________________________________________________________________\n", 213 | "g_conv_2 (Conv2D) (None, 16, 16, 16) 4112 \n", 214 | "_________________________________________________________________\n", 215 | "g_conv_3 (Conv2D) (None, 8, 8, 16) 2320 \n", 216 | "_________________________________________________________________\n", 217 | "conv2d_1 (Conv2D) (None, 4, 4, 16) 1040 \n", 218 | "_________________________________________________________________\n", 219 | "encoder (Conv2D) (None, 2, 2, 16) 272 \n", 220 | "_________________________________________________________________\n", 221 | "conv2d_transpose_1 (Conv2DTr (None, 4, 4, 16) 1040 \n", 222 | "_________________________________________________________________\n", 223 | "g_deconv_0 (Conv2DTranspose) (None, 8, 8, 16) 1040 \n", 224 | "_________________________________________________________________\n", 225 | "g_deconv_1 (Conv2DTranspose) (None, 16, 16, 16) 2320 \n", 226 | "_________________________________________________________________\n", 227 | "g_deconv_2 (Conv2DTranspose) (None, 32, 32, 16) 4112 \n", 228 | "_________________________________________________________________\n", 229 | "g_deconv_3 (Conv2DTranspose) (None, 64, 64, 16) 6416 \n", 230 | "_________________________________________________________________\n", 231 | "g_deconv_4 (Conv2DTranspose) (None, 64, 64, 3) 1731 \n", 232 | "=================================================================\n", 233 | "Total params: 32,563\n", 234 | "Trainable params: 32,563\n", 235 | "Non-trainable params: 0\n", 236 | "_________________________________________________________________\n" 237 | ] 238 | } 239 | ], 240 | "source": [ 241 | "autoencoder.summary()" 242 | ] 243 | }, 244 | { 245 | "cell_type": "markdown", 246 | "metadata": {}, 247 | "source": [ 248 | "### Training" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": 8, 254 | "metadata": { 255 | "scrolled": true 256 | }, 257 | "outputs": [ 258 | { 259 | "name": "stdout", 260 | "output_type": "stream", 261 | "text": [ 262 | "\n", 263 | "Epoch 00001: val_loss improved from inf to 0.02289, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 264 | "\n", 265 | "Epoch 00002: val_loss improved from 0.02289 to 0.01759, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 266 | "\n", 267 | "Epoch 00003: val_loss improved from 0.01759 to 0.01580, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 268 | "\n", 269 | "Epoch 00004: val_loss improved from 0.01580 to 0.01499, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 270 | "\n", 271 | "Epoch 00005: val_loss improved from 0.01499 to 0.01433, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 272 | "\n", 273 | "Epoch 00006: val_loss improved from 0.01433 to 0.01394, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 274 | "\n", 275 | "Epoch 00007: val_loss improved from 0.01394 to 0.01349, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 276 | "\n", 277 | "Epoch 00008: val_loss improved from 0.01349 to 0.01310, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 278 | "\n", 279 | "Epoch 00009: val_loss improved from 0.01310 to 0.01286, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 280 | "\n", 281 | "Epoch 00010: val_loss improved from 0.01286 to 0.01269, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 282 | "\n", 283 | "Epoch 00011: val_loss did not improve from 0.01269\n", 284 | "\n", 285 | "Epoch 00012: val_loss improved from 0.01269 to 0.01247, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 286 | "\n", 287 | "Epoch 00013: val_loss did not improve from 0.01247\n", 288 | "\n", 289 | "Epoch 00014: val_loss improved from 0.01247 to 0.01239, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 290 | "\n", 291 | "Epoch 00015: val_loss improved from 0.01239 to 0.01238, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 292 | "\n", 293 | "Epoch 00016: val_loss improved from 0.01238 to 0.01231, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 294 | "\n", 295 | "Epoch 00017: val_loss did not improve from 0.01231\n", 296 | "\n", 297 | "Epoch 00018: val_loss improved from 0.01231 to 0.01226, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 298 | "\n", 299 | "Epoch 00019: val_loss did not improve from 0.01226\n", 300 | "\n", 301 | "Epoch 00020: val_loss improved from 0.01226 to 0.01224, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 302 | "\n", 303 | "Epoch 00021: val_loss improved from 0.01224 to 0.01221, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 304 | "\n", 305 | "Epoch 00022: val_loss improved from 0.01221 to 0.01213, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 306 | "\n", 307 | "Epoch 00023: val_loss improved from 0.01213 to 0.01212, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 308 | "\n", 309 | "Epoch 00024: val_loss improved from 0.01212 to 0.01212, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 310 | "\n", 311 | "Epoch 00025: val_loss improved from 0.01212 to 0.01212, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 312 | "\n", 313 | "Epoch 00026: val_loss improved from 0.01212 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 314 | "\n", 315 | "Epoch 00027: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 316 | "\n", 317 | "Epoch 00028: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 318 | "\n", 319 | "Epoch 00029: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 320 | "\n", 321 | "Epoch 00030: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 322 | "\n", 323 | "Epoch 00031: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 324 | "\n", 325 | "Epoch 00032: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 326 | "\n", 327 | "Epoch 00033: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 328 | "\n", 329 | "Epoch 00034: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 330 | "\n", 331 | "Epoch 00035: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 332 | "\n", 333 | "Epoch 00036: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 334 | "\n", 335 | "Epoch 00037: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 336 | "\n", 337 | "Epoch 00038: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 338 | "\n", 339 | "Epoch 00039: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 340 | "\n", 341 | "Epoch 00040: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 342 | "\n", 343 | "Epoch 00041: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 344 | "\n", 345 | "Epoch 00042: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 346 | "\n", 347 | "Epoch 00043: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 348 | "\n", 349 | "Epoch 00044: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 350 | "\n", 351 | "Epoch 00045: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 352 | "\n", 353 | "Epoch 00046: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 354 | "\n", 355 | "Epoch 00047: val_loss improved from 0.01211 to 0.01211, saving model to cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3.h5\n", 356 | "\n", 357 | "Epoch 00048: val_loss did not improve from 0.01211\n", 358 | "\n", 359 | "Epoch 00049: val_loss did not improve from 0.01211\n", 360 | "\n", 361 | "Epoch 00050: val_loss did not improve from 0.01211\n", 362 | "\n", 363 | "Epoch 00051: val_loss did not improve from 0.01211\n", 364 | "\n", 365 | "Epoch 00052: val_loss did not improve from 0.01211\n", 366 | "\n", 367 | "Epoch 00053: val_loss did not improve from 0.01211\n", 368 | "\n", 369 | "Epoch 00054: val_loss did not improve from 0.01211\n", 370 | "\n", 371 | "Epoch 00055: val_loss did not improve from 0.01211\n", 372 | "\n", 373 | "Epoch 00056: val_loss did not improve from 0.01211\n", 374 | "\n", 375 | "Epoch 00057: val_loss did not improve from 0.01211\n", 376 | "Training done!\n" 377 | ] 378 | } 379 | ], 380 | "source": [ 381 | "train = autoencoder.fit(train_patches, train_patches,\n", 382 | " validation_data=(val_patches, val_patches),\n", 383 | " batch_size=batch_size,\n", 384 | " epochs=epochs,\n", 385 | " verbose=0,\n", 386 | " callbacks=[save_chkpt, stop_chkpt, lr_chkpt])\n", 387 | "print('Training done!')" 388 | ] 389 | }, 390 | { 391 | "cell_type": "markdown", 392 | "metadata": {}, 393 | "source": [ 394 | "# Deployment (test)" 395 | ] 396 | }, 397 | { 398 | "cell_type": "code", 399 | "execution_count": 17, 400 | "metadata": {}, 401 | "outputs": [], 402 | "source": [ 403 | "from sklearn.metrics import roc_curve, roc_auc_score\n", 404 | "from tqdm import tqdm" 405 | ] 406 | }, 407 | { 408 | "cell_type": "code", 409 | "execution_count": 18, 410 | "metadata": {}, 411 | "outputs": [], 412 | "source": [ 413 | "training = './cnn_article/giuriati_2_20170621_deg0_HHVV_Auto3D2_patch64_stride4_bsc5_ny3'\n", 414 | "net_weights = training + '.h5'" 415 | ] 416 | }, 417 | { 418 | "cell_type": "markdown", 419 | "metadata": {}, 420 | "source": [ 421 | "### Load dataset" 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": 19, 427 | "metadata": {}, 428 | "outputs": [], 429 | "source": [ 430 | "# in this case, the dataset for training in the same for testing\n", 431 | "train_path = in_path\n", 432 | "dataset = np.load('./datasets/' + str(in_path)).item()\n", 433 | "\n", 434 | "# preprocessing\n", 435 | "data = dataset['data']\n", 436 | "\n", 437 | "# patch extractor\n", 438 | "pe = PatchExtractor.PatchExtractor(patch_size, stride=patch_stride)\n", 439 | "\n", 440 | "# background bscans for training\n", 441 | "gt = np.asarray(dataset['ground_truth'])\n", 442 | "del dataset\n", 443 | "\n", 444 | "test_idx = np.arange(data.shape[0])\n", 445 | "# check whether the test dataset is the same of the training\n", 446 | "if in_path == train_path:\n", 447 | " train_idx = np.where(gt == 0)[0][:n_bsc]\n", 448 | " test_idx = np.delete(test_idx, train_idx)\n", 449 | "testset = data[test_idx]\n", 450 | "gt = gt[test_idx]\n", 451 | "del data\n", 452 | "testset = np.moveaxis(testset, np.argmin(testset.shape), -1)" 453 | ] 454 | }, 455 | { 456 | "cell_type": "markdown", 457 | "metadata": {}, 458 | "source": [ 459 | "### Load architecture" 460 | ] 461 | }, 462 | { 463 | "cell_type": "code", 464 | "execution_count": 20, 465 | "metadata": {}, 466 | "outputs": [], 467 | "source": [ 468 | "autoencoder, encoder = parse_net(architecture)(patch_size)\n", 469 | "autoencoder.load_weights(os.path.join(net_weights))\n", 470 | "\n", 471 | "out_name = field+'_'+campaign+'_'+architecture+'_patch'+str(patch_size[0])+'_stride'+str(patch_stride[0])+'_bsc'+str(n_bsc)+'_ny'+str(ny)" 472 | ] 473 | }, 474 | { 475 | "cell_type": "markdown", 476 | "metadata": {}, 477 | "source": [ 478 | "### Test loop" 479 | ] 480 | }, 481 | { 482 | "cell_type": "code", 483 | "execution_count": 21, 484 | "metadata": {}, 485 | "outputs": [], 486 | "source": [ 487 | "patches = pe.extract(testset)\n", 488 | "del testset\n", 489 | "patchesIdx = patches.shape\n", 490 | "patches_hat = autoencoder.predict(patches.reshape((-1,) + patch_size))\n", 491 | "mseFeat = (encoder.predict(patches.reshape((-1,) + patch_size)) - encoder.predict(patches_hat))**2\n", 492 | "mseFeat_patches = np.zeros(patches_hat.shape) + np.mean(mseFeat, axis=(1,2,3)).reshape((-1,1,1,1))\n", 493 | "del patches\n", 494 | "del patches_hat\n", 495 | "del mseFeat\n", 496 | "mseFeat_vol = pe.reconstruct(mseFeat_patches.reshape(patchesIdx))\n", 497 | "del mseFeat_patches" 498 | ] 499 | }, 500 | { 501 | "cell_type": "markdown", 502 | "metadata": {}, 503 | "source": [ 504 | "### Evaluation" 505 | ] 506 | }, 507 | { 508 | "cell_type": "code", 509 | "execution_count": 22, 510 | "metadata": {}, 511 | "outputs": [ 512 | { 513 | "name": "stdout", 514 | "output_type": "stream", 515 | "text": [ 516 | "best AUC = 0.93\n" 517 | ] 518 | } 519 | ], 520 | "source": [ 521 | "mse_mask_max = np.max(mseFeat_vol, axis=(0, 1))\n", 522 | "fpr_max, tpr_max, thresholds_max = roc_curve(gt, mse_mask_max)\n", 523 | "roc_auc_max = roc_auc_score(gt, mse_mask_max)\n", 524 | "print('best AUC = %0.2f' % roc_auc_max)" 525 | ] 526 | }, 527 | { 528 | "cell_type": "code", 529 | "execution_count": null, 530 | "metadata": {}, 531 | "outputs": [], 532 | "source": [] 533 | } 534 | ], 535 | "metadata": { 536 | "kernelspec": { 537 | "display_name": "Python 3", 538 | "language": "python", 539 | "name": "python3" 540 | }, 541 | "language_info": { 542 | "codemirror_mode": { 543 | "name": "ipython", 544 | "version": 3 545 | }, 546 | "file_extension": ".py", 547 | "mimetype": "text/x-python", 548 | "name": "python", 549 | "nbconvert_exporter": "python", 550 | "pygments_lexer": "ipython3", 551 | "version": "3.6.5" 552 | } 553 | }, 554 | "nbformat": 4, 555 | "nbformat_minor": 2 556 | } 557 | -------------------------------------------------------------------------------- /net.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Author: Francesco Picetti - francesco.picetti@polimi.it 3 | """ 4 | 5 | import os 6 | # change TensorFlow logging verbosity 7 | # 0: all logs 8 | # 1: filter out INFO logs 9 | # 2: filter out WARNINGS logs 10 | # 3: filter out ERROR logs 11 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' 12 | 13 | # use the GPU with the lowest memory usage 14 | import GPUtil 15 | DEVICE_ID = str(GPUtil.getFirstAvailable(order='memory')[0]) 16 | os.environ["CUDA_VISIBLE_DEVICES"] = DEVICE_ID 17 | print('GPU selected:', DEVICE_ID) 18 | 19 | 20 | import tensorflow as tf 21 | config = tf.ConfigProto() 22 | config.gpu_options.allow_growth = True 23 | session = tf.Session(config=config) 24 | 25 | import keras.backend as K 26 | K.set_session(session) 27 | 28 | from keras.layers import Input, Conv2D, Conv2DTranspose, BatchNormalization, Flatten, Dense, MaxPooling2D, Lambda 29 | from keras.models import Model 30 | from keras.optimizers import Adam, SGD 31 | 32 | 33 | class Settings: 34 | def __init__(self): 35 | self.patience = 10 36 | self.epochs = 100 37 | self.lr_factor = 0.1 38 | self.batch_size = 128 39 | 40 | 41 | def Auto1(patch_size, opt=Adam()): 42 | """ 43 | Autoencoder with a hidden representation of 32 elements 44 | """ 45 | img = Input(shape=patch_size, name='g_in_0') 46 | 47 | x = Conv2D(16, (6, 6), strides=1, padding='same', name='g_conv_0')(img) 48 | x = Conv2D(16, (5, 5), strides=2, padding='same', name='g_conv_1')(x) 49 | x = Conv2D(16, (4, 4), strides=2, padding='same', name='g_conv_2')(x) 50 | x = Conv2D(16, (3, 3), strides=2, padding='same', name='g_conv_3')(x) 51 | 52 | enc = Conv2D(8, (1, 1), strides=2, padding='same', name='encoder')(x) 53 | 54 | x = Conv2DTranspose(16, (2, 2), strides=2, padding='same')(enc) 55 | x = Conv2DTranspose(16, (3, 3), strides=2, padding='same', name='g_deconv_1')(x) 56 | x = Conv2DTranspose(16, (4, 4), strides=2, padding='same', name='g_deconv_2')(x) 57 | x = Conv2DTranspose(16, (5, 5), strides=2, padding='same', name='g_deconv_3')(x) 58 | 59 | dec = Conv2DTranspose(1, (6, 6), strides=1, padding='same', 60 | activation='tanh', name='g_deconv_4')(x) 61 | 62 | autoencoder = Model(inputs=img, outputs=dec) 63 | encoder = Model(inputs=img, outputs=enc) 64 | 65 | autoencoder.compile(loss='mean_squared_error', optimizer=opt) 66 | 67 | return autoencoder, encoder 68 | 69 | 70 | 71 | def Auto2(patch_size, opt=Adam()): 72 | """ 73 | Autoencoder with a hidden representation of 16 elements 74 | """ 75 | img = Input(shape=patch_size, name='g_in_0') 76 | 77 | x = Conv2D(16, (6, 6), strides=1, padding='same', name='g_conv_0')(img) 78 | x = Conv2D(16, (5, 5), strides=2, padding='same', name='g_conv_1')(x) 79 | x = Conv2D(16, (4, 4), strides=2, padding='same', name='g_conv_2')(x) 80 | x = Conv2D(16, (3, 3), strides=2, padding='same', name='g_conv_3')(x) 81 | x = Conv2D(16, (2, 2), strides=2, padding='same')(x) 82 | 83 | enc = Conv2D(16, (1, 1), strides=2, padding='same', name='encoder')(x) 84 | 85 | x = Conv2DTranspose(16, (2, 2), strides=2, padding='same')(enc) 86 | x = Conv2DTranspose(16, (2, 2), strides=2, padding='same', name='g_deconv_0')(x) 87 | x = Conv2DTranspose(16, (3, 3), strides=2, padding='same', name='g_deconv_1')(x) 88 | x = Conv2DTranspose(16, (4, 4), strides=2, padding='same', name='g_deconv_2')(x) 89 | x = Conv2DTranspose(16, (5, 5), strides=2, padding='same', name='g_deconv_3')(x) 90 | 91 | dec = Conv2DTranspose(1, (6, 6), strides=1, padding='same', 92 | activation='tanh', name='g_deconv_4')(x) 93 | 94 | autoencoder = Model(inputs=img, outputs=dec) 95 | encoder = Model(inputs=img, outputs=enc) 96 | 97 | autoencoder.compile(loss='mean_squared_error', optimizer=opt) 98 | 99 | return autoencoder, encoder 100 | 101 | 102 | 103 | def Auto3(patch_size, opt=Adam()): 104 | """ 105 | Autoencoder with a hidden representation of 64 elements 106 | """ 107 | img = Input(shape=patch_size, name='g_in_0') 108 | 109 | x = Conv2D(16, (6, 6), strides=1, padding='same', name='g_conv_0')(img) 110 | x = Conv2D(16, (5, 5), strides=2, padding='same', name='g_conv_1')(x) 111 | x = Conv2D(16, (4, 4), strides=2, padding='same', name='g_conv_2')(x) 112 | x = Conv2D(16, (3, 3), strides=2, padding='same', name='g_conv_3')(x) 113 | 114 | enc = Conv2D(16, (2, 2), strides=2, padding='same', name='encoder')(x) 115 | 116 | x = Conv2DTranspose(16, (2, 2), strides=2, padding='same', name='g_deconv_0')(enc) 117 | x = Conv2DTranspose(16, (3, 3), strides=2, padding='same', name='g_deconv_1')(x) 118 | x = Conv2DTranspose(16, (4, 4), strides=2, padding='same', name='g_deconv_2')(x) 119 | x = Conv2DTranspose(16, (5, 5), strides=2, padding='same', name='g_deconv_3')(x) 120 | 121 | dec = Conv2DTranspose(1, (6, 6), strides=1, padding='same', 122 | activation='tanh', name='g_deconv_4')(x) 123 | 124 | autoencoder = Model(inputs=img, outputs=dec) 125 | encoder = Model(inputs=img, outputs=enc) 126 | 127 | autoencoder.compile(loss='mean_squared_error', optimizer=opt) 128 | 129 | return autoencoder, encoder 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | def Auto3D1(patch_size, opt=Adam()): 138 | """ 139 | Autoencoder with a hidden representation of 32 elements 140 | """ 141 | img = Input(shape=patch_size, name='g_in_0') 142 | 143 | x = Conv2D(16, (6, 6), strides=1, padding='same', name='g_conv_0')(img) 144 | x = Conv2D(16, (5, 5), strides=2, padding='same', name='g_conv_1')(x) 145 | x = Conv2D(16, (4, 4), strides=2, padding='same', name='g_conv_2')(x) 146 | x = Conv2D(16, (3, 3), strides=2, padding='same', name='g_conv_3')(x) 147 | 148 | enc = Conv2D(8, (1, 1), strides=2, padding='same', name='encoder')(x) 149 | 150 | x = Conv2DTranspose(16, (2, 2), strides=2, padding='same')(enc) 151 | x = Conv2DTranspose(16, (3, 3), strides=2, padding='same', name='g_deconv_1')(x) 152 | x = Conv2DTranspose(16, (4, 4), strides=2, padding='same', name='g_deconv_2')(x) 153 | x = Conv2DTranspose(16, (5, 5), strides=2, padding='same', name='g_deconv_3')(x) 154 | 155 | dec = Conv2DTranspose(3, (6, 6), strides=1, padding='same', 156 | activation='tanh', name='g_deconv_4')(x) 157 | 158 | autoencoder = Model(inputs=img, outputs=dec) 159 | encoder = Model(inputs=img, outputs=enc) 160 | 161 | autoencoder.compile(loss='mean_squared_error', optimizer=opt) 162 | 163 | return autoencoder, encoder 164 | 165 | 166 | def Auto3D2(patch_size, opt=Adam()): 167 | """ 168 | Autoencoder with a hidden representation of 16 elements 169 | """ 170 | img = Input(shape=patch_size, name='g_in_0') 171 | 172 | x = Conv2D(16, (6, 6), strides=1, padding='same', name='g_conv_0')(img) 173 | x = Conv2D(16, (5, 5), strides=2, padding='same', name='g_conv_1')(x) 174 | x = Conv2D(16, (4, 4), strides=2, padding='same', name='g_conv_2')(x) 175 | x = Conv2D(16, (3, 3), strides=2, padding='same', name='g_conv_3')(x) 176 | x = Conv2D(16, (2, 2), strides=2, padding='same')(x) 177 | 178 | enc = Conv2D(16, (1, 1), strides=2, padding='same', name='encoder')(x) 179 | 180 | x = Conv2DTranspose(16, (2, 2), strides=2, padding='same')(enc) 181 | x = Conv2DTranspose(16, (2, 2), strides=2, padding='same', name='g_deconv_0')(x) 182 | x = Conv2DTranspose(16, (3, 3), strides=2, padding='same', name='g_deconv_1')(x) 183 | x = Conv2DTranspose(16, (4, 4), strides=2, padding='same', name='g_deconv_2')(x) 184 | x = Conv2DTranspose(16, (5, 5), strides=2, padding='same', name='g_deconv_3')(x) 185 | 186 | dec = Conv2DTranspose(3, (6, 6), strides=1, padding='same', 187 | activation='tanh', name='g_deconv_4')(x) 188 | 189 | autoencoder = Model(inputs=img, outputs=dec) 190 | encoder = Model(inputs=img, outputs=enc) 191 | 192 | autoencoder.compile(loss='mean_squared_error', optimizer=opt) 193 | 194 | return autoencoder, encoder 195 | 196 | 197 | 198 | def Auto3D3(patch_size, opt=Adam()): 199 | """ 200 | Autoencoder with a hidden representation of 64 elements 201 | """ 202 | img = Input(shape=patch_size, name='g_in_0') 203 | 204 | x = Conv2D(16, (6, 6), strides=1, padding='same', name='g_conv_0')(img) 205 | x = Conv2D(16, (5, 5), strides=2, padding='same', name='g_conv_1')(x) 206 | x = Conv2D(16, (4, 4), strides=2, padding='same', name='g_conv_2')(x) 207 | x = Conv2D(16, (3, 3), strides=2, padding='same', name='g_conv_3')(x) 208 | 209 | enc = Conv2D(16, (2, 2), strides=2, padding='same', name='encoder')(x) 210 | 211 | x = Conv2DTranspose(16, (2, 2), strides=2, padding='same', name='g_deconv_0')(enc) 212 | x = Conv2DTranspose(16, (3, 3), strides=2, padding='same', name='g_deconv_1')(x) 213 | x = Conv2DTranspose(16, (4, 4), strides=2, padding='same', name='g_deconv_2')(x) 214 | x = Conv2DTranspose(16, (5, 5), strides=2, padding='same', name='g_deconv_3')(x) 215 | 216 | dec = Conv2DTranspose(3, (6, 6), strides=1, padding='same', 217 | activation='tanh', name='g_deconv_4')(x) 218 | 219 | autoencoder = Model(inputs=img, outputs=dec) 220 | encoder = Model(inputs=img, outputs=enc) 221 | 222 | autoencoder.compile(loss='mean_squared_error', optimizer=opt) 223 | 224 | return autoencoder, encoder 225 | --------------------------------------------------------------------------------