├── Code.py ├── LICENSE └── README.md /Code.py: -------------------------------------------------------------------------------- 1 | '''PRE-PROCESSING''' 2 | 3 | import sys 4 | sys.path.append('C:\\.snap\\snap-python\\snappy') 5 | import snappy 6 | 7 | 8 | from os.path import join 9 | import subprocess 10 | from glob import iglob 11 | from zipfile import ZipFile 12 | import numpy as np 13 | import pandas as pd 14 | import snappy 15 | from snappy import jpy, GPF, ProductIO 16 | import matplotlib.pyplot as plt 17 | 18 | 19 | #set target folder and extract metadata 20 | #product_path = '...\\S-1_folder' #folder where S-1.zip are stored 21 | product_path = 'D:\\Prova' 22 | input_S1_files = sorted(list(iglob(join(product_path, '**', '*S1*.zip'), recursive=True))) 23 | name, sensing_mode, product_type, polarization, height, width, band_names = ([] for i in range(7)) 24 | 25 | s1_data = [] 26 | for i in input_S1_files: 27 | sensing_mode.append(i.split("_")[3]) 28 | product_type.append(i.split("_")[4]) 29 | polarization.append(i.split("_")[-6]) 30 | s1_read = snappy.ProductIO.readProduct(i) 31 | name.append(s1_read.getName()) 32 | height.append(s1_read.getSceneRasterHeight()) 33 | width.append(s1_read.getSceneRasterWidth()) 34 | band_names.append(s1_read.getBandNames()) 35 | s1_data.append(s1_read) 36 | 37 | 38 | def read(filename): 39 | return ProductIO.readProduct(filename) 40 | 41 | def write(product, filename): 42 | ProductIO.writeProduct(product, filename, "BEAM-DIMAP") 43 | 44 | 45 | 46 | '''APPLY ORBIT FILE''' 47 | def apply_orbit_file(product): 48 | parameters = snappy.HashMap() 49 | parameters.put('Apply-Orbit-File', True) 50 | return snappy.GPF.createProduct("Apply-Orbit-File", parameters, product) 51 | 52 | 53 | 54 | '''THERMAL NOISE REMOVAL''' 55 | def thermal_noise_removal(product): 56 | parameters = snappy.HashMap() 57 | parameters.put('removeThermalNoise', True) 58 | return snappy.GPF.createProduct('thermalNoiseRemoval', parameters, product) 59 | 60 | 61 | '''CALIBRATION''' 62 | def calibration(product): 63 | parameters = snappy.HashMap() 64 | parameters.put('selectedPolarisations', 'VH,VV') 65 | parameters.put('outputBetaBand', True) 66 | parameters.put('outputSigmaBand', False) 67 | parameters.put('outputImageScaleInDb', False) 68 | return snappy.GPF.createProduct('Calibration', parameters, product) 69 | 70 | 71 | '''TERRAIN FLATTENING''' 72 | def terrain_flattening(product): 73 | parameters = snappy.HashMap() 74 | return snappy.GPF.createProduct('Terrain-Flattening', parameters, product) 75 | 76 | 77 | '''TERRAIN CORRECTION''' 78 | proj_PO = '''PROJCS["WGS 84 / UTM zone 29N", 79 | GEOGCS["WGS 84", 80 | DATUM["WGS_1984", 81 | SPHEROID["WGS 84",6378137,298.257223563, 82 | AUTHORITY["EPSG","7030"]], 83 | AUTHORITY["EPSG","6326"]], 84 | PRIMEM["Greenwich",0.0, 85 | AUTHORITY["EPSG","8901"]], 86 | UNIT["degree",0.0174532925199433, 87 | AUTHORITY["EPSG","9122"]], 88 | AUTHORITY["EPSG","4326"]], 89 | PROJECTION["Transverse_Mercator"], 90 | PARAMETER["latitude_of_origin",0], 91 | PARAMETER["central_meridian",-9], 92 | PARAMETER["scale_factor",0.9996], 93 | PARAMETER["false_easting",500000], 94 | PARAMETER["false_northing",0], 95 | UNIT["m",1, AUTHORITY["EPSG","9001"]], 96 | AXIS["Easting",EAST], 97 | AXIS["Northing",NORTH], 98 | AUTHORITY["EPSG","32629"]]''' 99 | 100 | proj_IT = '''PROJCS["WGS 84 / UTM zone 33N", 101 | GEOGCS["WGS 84", 102 | DATUM["WGS_1984", 103 | SPHEROID["WGS 84",6378137,298.257223563, 104 | AUTHORITY["EPSG","7030"]], 105 | AUTHORITY["EPSG","6326"]], 106 | PRIMEM["Greenwich",0.0, 107 | AUTHORITY["EPSG","8901"]], 108 | UNIT["degree",0.0174532925199433, 109 | AUTHORITY["EPSG","9122"]], 110 | AUTHORITY["EPSG","4326"]], 111 | PROJECTION["Transverse_Mercator"], 112 | PARAMETER["latitude_of_origin",0], 113 | PARAMETER["central_meridian",15], 114 | PARAMETER["scale_factor",0.9996], 115 | PARAMETER["false_easting",500000], 116 | PARAMETER["false_northing",0], 117 | UNIT["m",1, AUTHORITY["EPSG","9001"]], 118 | AXIS["Easting",EAST], 119 | AXIS["Northing",NORTH], 120 | AUTHORITY["EPSG","32633"]]''' 121 | 122 | 123 | def terrain_correction(product, proj): 124 | parameters = snappy.HashMap() 125 | parameters.put('demName', 'SRTM 1Sec HGT') 126 | parameters.put('pixelSpacingInMeter', 10.0) 127 | parameters.put('mapProjection', proj) 128 | parameters.put('nodataValueAtSea', False) 129 | return snappy.GPF.createProduct('Terrain-Correction', parameters, product) 130 | 131 | 132 | 133 | '''SUBSET''' 134 | wkt_PO= 'POLYGON((-8.76874876510233392 37.15010193049546672, -8.76734497314194883 37.60592873585844131, -7.96268434574197403 37.60159916951047876, -7.96939235295303483 37.14584665530825447, -8.76874876510233392 37.15010193049546672))' 135 | geom_PO = snappy.WKTReader().read(wkt_PO) 136 | 137 | def subset(product, geom): 138 | parameters = snappy.HashMap() 139 | parameters.put('geoRegion', geom) 140 | parameters.put('copyMetadata', True) 141 | return snappy.GPF.createProduct('Subset', parameters, product) 142 | 143 | 144 | 145 | '''COREGISTRATION: CREATE STACK''' 146 | def create_stack(product): 147 | parameters = snappy.HashMap() 148 | parameters.put('extent','Master') 149 | return snappy.GPF.createProduct("CreateStack", parameters, product) 150 | 151 | 152 | '''MULTITEMPORAL SPECKLE FILTER''' 153 | 154 | def multitemporal_filter(product): 155 | parameters = snappy.HashMap() 156 | parameters.put('filter', 'Lee') 157 | parameters.put('filterSizeX', 10) 158 | parameters.put('filterSizeY', 10) 159 | return snappy.GPF.createProduct('Multi-Temporal-Speckle-Filter', parameters, product) 160 | 161 | 162 | 163 | '''GLCM''' 164 | def GLCM(product): 165 | parameters = snappy.HashMap() 166 | parameters.put('windowSizeStr', '11x11') 167 | parameters.put('outputContrast', False) 168 | parameters.put('outputASM', False) 169 | parameters.put('outputEnergy', False) 170 | parameters.put('outputHomogeneity', False) 171 | parameters.put('outputMAX', False) 172 | return snappy.GPF.createProduct('GLCM', parameters, product) 173 | 174 | 175 | '''Process''' 176 | 177 | def SAR_preprocessing_workflow(collection): 178 | pre_list = [] 179 | for i in collection: 180 | a = apply_orbit_file(i) 181 | b = thermal_noise_removal(a) 182 | c = calibration(b) 183 | d = terrain_flattening(c) 184 | e = terrain_correction(d, proj_PO) 185 | f = subset(e, geom_PO) 186 | pre_list.append(f) 187 | return pre_list 188 | 189 | PL = SAR_preprocessing_workflow(s1_data) 190 | stack = create_stack(PL) 191 | filtered = multitemporal_filter(stack) 192 | 193 | 194 | bands = filtered.getBandNames() 195 | #print("Bands:%s" % (list(bands))) 196 | bands_name= list(bands) 197 | bands_name 198 | 199 | 200 | '''Creation of Time-Average''' 201 | BandDescriptor = jpy.get_type('org.esa.snap.core.gpf.common.BandMathsOp$BandDescriptor') 202 | targetBand1 = BandDescriptor() 203 | targetBand1.name = 'TimeAverage_VH_PreFire' 204 | targetBand1.type = 'float32' 205 | targetBand1.expression = '(Gamma0_VH_mst_30Aug2018 + Gamma0_VH_slv1_18Aug2018) / 2' 206 | 207 | targetBand2 = BandDescriptor() 208 | targetBand2.name = 'TimeAverage_VH_PostFire' 209 | targetBand2.type = 'float32' 210 | targetBand2.expression = '(Gamma0_VH_mst_30Aug2018 + Gamma0_VH_slv1_18Aug2018) / 2' 211 | 212 | targetBand3 = BandDescriptor() 213 | targetBand3.name = 'TimeAverage_VV_PreFire' 214 | targetBand3.type = 'float32' 215 | targetBand3.expression = '(Gamma0_VH_mst_30Aug2018 + Gamma0_VH_slv1_18Aug2018) / 2' 216 | 217 | targetBand4 = BandDescriptor() 218 | targetBand4.name = 'TimeAverage_VV_PostFire' 219 | targetBand4.type = 'float32' 220 | targetBand4.expression = '(Gamma0_VH_mst_30Aug2018 + Gamma0_VH_slv1_18Aug2018) / 2' 221 | 222 | targetBands = jpy.array('org.esa.snap.core.gpf.common.BandMathsOp$BandDescriptor', 4) 223 | targetBands[0] = targetBand1 224 | targetBands[1] = targetBand2 225 | targetBands[2] = targetBand3 226 | targetBands[3] = targetBand4 227 | 228 | parameters = snappy.HashMap() 229 | parameters.put('targetBands', targetBands) 230 | 231 | TimeAverage = GPF.createProduct('BandMaths', parameters, filtered) 232 | 233 | 234 | bands = TimeAverage.getBandNames() 235 | #print("Bands:%s" % (list(bands))) 236 | bands_name= list(bands) 237 | bands_name 238 | 239 | 240 | 241 | '''Creation of Time-Average''' 242 | BandDescriptor = jpy.get_type('org.esa.snap.core.gpf.common.BandMathsOp$BandDescriptor') 243 | targetBand1 = BandDescriptor() 244 | targetBand1.name = 'RBD_VH' 245 | targetBand1.type = 'float32' 246 | targetBand1.expression = 'TimeAverage_VH_PostFire - TimeAverage_VH_PreFire' 247 | 248 | targetBand2 = BandDescriptor() 249 | targetBand2.name = 'RBD_VV' 250 | targetBand2.type = 'float32' 251 | targetBand2.expression = 'TimeAverage_VV_PostFire - TimeAverage_VV_PreFire' 252 | 253 | targetBand3 = BandDescriptor() 254 | targetBand3.name = 'LogRBR_VH' 255 | targetBand3.type = 'float32' 256 | targetBand3.expression = 'log10(TimeAverage_VH_PostFire / TimeAverage_VH_PreFire)' 257 | 258 | targetBand4 = BandDescriptor() 259 | targetBand4.name = 'LogRBR_VV' 260 | targetBand4.type = 'float32' 261 | targetBand4.expression = 'log10(TimeAverage_VV_PostFire / TimeAverage_VV_PreFire)' 262 | 263 | RVI_post = '4 * TimeAverage_VH_PostFire/(TimeAverage_VV_PostFire + TimeAverage_VH_PostFire)' 264 | RVI_pre = '4 * TimeAverage_VH_PreFire/(TimeAverage_VV_PreFire + TimeAverage_VH_PreFire)' 265 | DPSVI_post = '(TimeAverage_VV_PostFire + TimeAverage_VH_PostFire)/TimeAverage_VV_PostFire' 266 | DPSVI_pre = '(TimeAverage_VV_PreFire + TimeAverage_VH_PreFire)/TimeAverage_VV_PreFire' 267 | 268 | targetBand5 = BandDescriptor() 269 | targetBand5.name = 'DeltaRVI' 270 | targetBand5.type = 'float32' 271 | targetBand5.expression = RVI_post + '-' + RVI_pre 272 | 273 | targetBand6 = BandDescriptor() 274 | targetBand6.name = 'DeltaDPSVI' 275 | targetBand6.type = 'float32' 276 | targetBand6.expression = DPSVI_post + '-' + DPSVI_pre 277 | 278 | targetBands = jpy.array('org.esa.snap.core.gpf.common.BandMathsOp$BandDescriptor', 6) 279 | targetBands[0] = targetBand1 280 | targetBands[1] = targetBand2 281 | targetBands[2] = targetBand3 282 | targetBands[3] = targetBand4 283 | targetBands[4] = targetBand5 284 | targetBands[5] = targetBand6 285 | 286 | parameters = snappy.HashMap() 287 | parameters.put('targetBands', targetBands) 288 | 289 | Indices = GPF.createProduct('BandMaths', parameters, TimeAverage) 290 | 291 | 292 | bands = Indices.getBandNames() 293 | #print("Bands:%s" % (list(bands))) 294 | bands_name= list(bands) 295 | bands_name 296 | 297 | 298 | '''GLCM''' 299 | glcm = GLCM(Indices) 300 | 301 | 302 | '''WRITING''' 303 | write(glcm, 'C:folder\\dataset_output.dim') 304 | 305 | 306 | ======================================================= 307 | 308 | '''PROCESSING''' 309 | from osgeo import gdal, osr 310 | import json, re, itertools, os 311 | import numpy as np 312 | import pandas as pd 313 | import matplotlib.pyplot as plt 314 | from mpl_toolkits.mplot3d import Axes3D 315 | import seaborn as sns 316 | 317 | 318 | "Open the S-1 Dataset" 319 | 320 | directory = r'C:folder\\dataset_output.data' 321 | 322 | list_files = [] 323 | list_bands = [] 324 | dirFileList = os.listdir(directory) 325 | 326 | os.chdir(directory) 327 | for file in dirFileList: 328 | if os.path.splitext(file)[-1] == '.img': 329 | list_files.append(os.path.join(directory, file)) 330 | 331 | for file in list_files: 332 | img = gdal.Open(file) 333 | band = img.GetRasterBand(1).ReadAsArray() 334 | list_bands.append(band) 335 | 336 | list_files 337 | list_bands 338 | 339 | 340 | 341 | '''Reshaping''' 342 | colxrig = list_bands[0].shape[0] * list_bands[0].shape[1] 343 | vectors = [band.reshape(colxrig,1) for band in list_bands] 344 | reshapedDataset = np.array(vectors).reshape(len(list_bands),colxrig).transpose() 345 | 346 | 347 | 348 | 349 | '''Rescale ''' 350 | from sklearn.preprocessing import MinMaxScaler 351 | 352 | normal = MinMaxScaler() 353 | dataset= normal.fit_transform(reshapedDataset) 354 | 355 | 356 | 357 | 358 | '''Principal Component Analysis''' 359 | from sklearn.decomposition import PCA 360 | 361 | pca = PCA(n_components= reshapedDataset.shape[1]).fit(dataset) 362 | pca_dataset = pca.transform(dataset) 363 | 364 | 365 | 366 | 367 | '''Chose the first PCs that reaches 99% of cumulative variance''' 368 | cumulative_variance = np.cumsum(pca.explained_variance_ratio_) 369 | subset99 = cumulative_variance[cumulative_variance<0.99] 370 | subset99 = np.append(subset99, cumulative_variance[cumulative_variance>=0.99][0]) 371 | n_pcs = len(subset99) 372 | pc_to_classify = pca_dataset[:,:n_pcs] 373 | 374 | 375 | 376 | 377 | '''Silhouette Score''' 378 | from sklearn import preprocessing 379 | from sklearn.cluster import KMeans 380 | from sklearn.metrics import silhouette_score 381 | 382 | k_space = list(range(2,21)) 383 | k_space 384 | 385 | results, scores = [], [] 386 | sample = np.random.choice(range(colxrig), size=100000) 387 | for k in k_space: 388 | print("Running k-means with k=%d..." % k) 389 | model_k = KMeans(k, init='k-means++', algorithm='full') 390 | model_k.fit(pc_to_classify) 391 | clusters_k = model_k.labels_ 392 | results.append(clusters_k.reshape(list_bands[0].shape)) 393 | silhouetteScore = silhouette_score(reshapedDataset[sample,:], clusters_k[sample]) 394 | scores.append(silhouetteScore) 395 | print("Found %d clusters with an average Silhouette Score of %.3f" % (k, silhouetteScore)) 396 | 397 | 398 | 399 | '''K-MEAN classification''' 400 | model = KMeans(n_clusters=7, init='k-means++', algorithm='full') #l'algoritmo utilizzato è il k-mean++ 401 | model.fit(pc_to_classify) 402 | clusters = model.labels_ 403 | 404 | #show the image classified 405 | classified_image = clusters.reshape(lista_bande[0].shape) 406 | plt.figure() 407 | plt.imshow(classified_image) 408 | plt.show() 409 | 410 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sentinel-1-unsupervised-burned-area-detection 2 | This code represents a processing workflow for the unsupervised detection and mapping of burned areas using SAR Sentinel-1 (S-1) satellite data (https://sentinel.esa.int/web/sentinel/missions/sentinel-1). It was developed by implementing ESA-snappy (https://senbox.atlassian.net/wiki/spaces/SNAP/pages/24051769/Cookbook) for image pre-processing and Scikit-learn (https://scikit-learn.org/) for processing and classification. It consists of the following fundamental main steps: [A] using ESA-snappy: 1) open raw S-1 data; 2) S-1 pre-processing; backscatter Time-Average for pre- and post-fire period; 3) calculation of the SAR indices [burn difference (RBD), the logarithmic RADAR burn ratio (LogRBR), the delta radar vegetation index (ΔRVI) and the dual dual-polarization SAR vegetation index ΔDPSVI); 4) gray-level co-occurrence matrix (GLCM) texture features calculation. [B] Using Scikit-learn: 5) principal components analysis (PCA) transformation; 6) silhouette score analysis to set the k parameter value; 7) unsupervised classification using the k-means algorithm. 3 | --------------------------------------------------------------------------------