├── .gitignore ├── README.md └── s1_preprocessing.py /.gitignore: -------------------------------------------------------------------------------- 1 | data 2 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This repository contains Python script for Sentinel-1 image pre-processing using snappy. 2 | 3 | 1. Before running the code, you need to install the [Sentinel Toolbox Application (SNAP)](https://step.esa.int/main/download/snap-download/) and [configure Python to use the SNAP-Python (snappy) interface](https://senbox.atlassian.net/wiki/spaces/SNAP/pages/50855941/Configure+Python+to+use+the+SNAP-Python+snappy+interface) 4 | 5 | 2. The code reads in unzipped Sentinel-1 GRD products (EW and IW modes). 6 | - Sentinel-1 images can be downloaded from: 7 | - [Sentinels Scientific Data Hub](https://scihub.copernicus.eu/dhus/#/home) or 8 | - [Alaska Satellite Facility](https://vertex.daac.asf.alaska.edu/#) 9 | 3. Sentinel-1 pre-processing steps: 10 | 11 | (1) Apply orbit file 12 | (2) Thermal noise removal 13 | (3) Radiometric calibration 14 | (4) Speckle filtering 15 | (5) Terrain correction 16 | (6) Subset 17 | 18 | 19 | This is the general pre-processing steps for Sentinel-1. Since each step is written in a separate function, it can be cutomized based on user needs (Tips: If you would like to modify the parameters, you can refer to the graph builder file (.xml) which can be created in the Graph Builder of SNAP software.). 20 | IW images are downsampled from 10m to 40m (the same resolution as EW images) in the terrain correction step. 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /s1_preprocessing.py: -------------------------------------------------------------------------------- 1 | # Need to configure Python to use the SNAP-Python (snappy) interface(https://senbox.atlassian.net/wiki/spaces/SNAP/pages/50855941/Configure+Python+to+use+the+SNAP-Python+snappy+interface) 2 | # Read in unzipped Sentinel-1 GRD products (EW and IW modes) 3 | 4 | import datetime 5 | import time 6 | from snappy import ProductIO 7 | from snappy import HashMap 8 | import os, gc 9 | from snappy import GPF 10 | 11 | def do_apply_orbit_file(source): 12 | print('\tApply orbit file...') 13 | parameters = HashMap() 14 | parameters.put('Apply-Orbit-File', True) 15 | output = GPF.createProduct('Apply-Orbit-File', parameters, source) 16 | return output 17 | 18 | def do_thermal_noise_removal(source): 19 | print('\tThermal noise removal...') 20 | parameters = HashMap() 21 | parameters.put('removeThermalNoise', True) 22 | output = GPF.createProduct('ThermalNoiseRemoval', parameters, source) 23 | return output 24 | 25 | def do_calibration(source, polarization, pols): 26 | print('\tCalibration...') 27 | parameters = HashMap() 28 | parameters.put('outputSigmaBand', True) 29 | if polarization == 'DH': 30 | parameters.put('sourceBands', 'Intensity_HH,Intensity_HV') 31 | elif polarization == 'DV': 32 | parameters.put('sourceBands', 'Intensity_VH,Intensity_VV') 33 | elif polarization == 'SH' or polarization == 'HH': 34 | parameters.put('sourceBands', 'Intensity_HH') 35 | elif polarization == 'SV': 36 | parameters.put('sourceBands', 'Intensity_VV') 37 | else: 38 | print("different polarization!") 39 | parameters.put('selectedPolarisations', pols) 40 | parameters.put('outputImageScaleInDb', False) 41 | output = GPF.createProduct("Calibration", parameters, source) 42 | return output 43 | 44 | def do_speckle_filtering(source): 45 | print('\tSpeckle filtering...') 46 | parameters = HashMap() 47 | parameters.put('filter', 'Lee') 48 | parameters.put('filterSizeX', 5) 49 | parameters.put('filterSizeY', 5) 50 | output = GPF.createProduct('Speckle-Filter', parameters, source) 51 | return output 52 | 53 | def do_terrain_correction(source, proj, downsample): 54 | print('\tTerrain correction...') 55 | parameters = HashMap() 56 | parameters.put('demName', 'GETASSE30') 57 | parameters.put('imgResamplingMethod', 'BILINEAR_INTERPOLATION') 58 | parameters.put('mapProjection', proj) # comment this line if no need to convert to UTM/WGS84, default is WGS84 59 | parameters.put('saveProjectedLocalIncidenceAngle', True) 60 | parameters.put('saveSelectedSourceBand', True) 61 | while downsample == 1: # downsample: 1 -- need downsample to 40m, 0 -- no need to downsample 62 | parameters.put('pixelSpacingInMeter', 40.0) 63 | break 64 | output = GPF.createProduct('Terrain-Correction', parameters, source) 65 | return output 66 | 67 | def do_subset(source, wkt): 68 | print('\tSubsetting...') 69 | parameters = HashMap() 70 | parameters.put('geoRegion', wkt) 71 | output = GPF.createProduct('Subset', parameters, source) 72 | return output 73 | 74 | def main(): 75 | ## All Sentinel-1 data sub folders are located within a super folder (make sure the data is already unzipped and each sub folder name ends with '.SAFE'): 76 | path = r'data\s1_images' 77 | outpath = r'data\s1_preprocessed' 78 | if not os.path.exists(outpath): 79 | os.makedirs(outpath) 80 | ## well-known-text (WKT) file for subsetting (can be obtained from SNAP by drawing a polygon) 81 | wkt = 'POLYGON ((-157.79579162597656 71.36872100830078, -155.4447021484375 71.36872100830078, \ 82 | -155.4447021484375 70.60020446777344, -157.79579162597656 70.60020446777344, -157.79579162597656 71.36872100830078))' 83 | ## UTM projection parameters 84 | proj = '''PROJCS["UTM Zone 4 / World Geodetic System 1984",GEOGCS["World Geodetic System 1984",DATUM["World Geodetic System 1984",SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],UNIT["degree", 0.017453292519943295],AXIS["Geodetic longitude", EAST],AXIS["Geodetic latitude", NORTH]],PROJECTION["Transverse_Mercator"],PARAMETER["central_meridian", -159.0],PARAMETER["latitude_of_origin", 0.0],PARAMETER["scale_factor", 0.9996],PARAMETER["false_easting", 500000.0],PARAMETER["false_northing", 0.0],UNIT["m", 1.0],AXIS["Easting", EAST],AXIS["Northing", NORTH]]''' 85 | 86 | for folder in os.listdir(path): 87 | gc.enable() 88 | gc.collect() 89 | sentinel_1 = ProductIO.readProduct(path + "\\" + folder + "\\manifest.safe") 90 | print(sentinel_1) 91 | 92 | loopstarttime=str(datetime.datetime.now()) 93 | print('Start time:', loopstarttime) 94 | start_time = time.time() 95 | 96 | ## Extract mode, product type, and polarizations from filename 97 | modestamp = folder.split("_")[1] 98 | productstamp = folder.split("_")[2] 99 | polstamp = folder.split("_")[3] 100 | 101 | polarization = polstamp[2:4] 102 | if polarization == 'DV': 103 | pols = 'VH,VV' 104 | elif polarization == 'DH': 105 | pols = 'HH,HV' 106 | elif polarization == 'SH' or polarization == 'HH': 107 | pols = 'HH' 108 | elif polarization == 'SV': 109 | pols = 'VV' 110 | else: 111 | print("Polarization error!") 112 | 113 | ## Start preprocessing: 114 | applyorbit = do_apply_orbit_file(sentinel_1) 115 | thermaremoved = do_thermal_noise_removal(applyorbit) 116 | calibrated = do_calibration(thermaremoved, polarization, pols) 117 | down_filtered = do_speckle_filtering(calibrated) 118 | del applyorbit 119 | del thermaremoved 120 | del calibrated 121 | ## IW images are downsampled from 10m to 40m (the same resolution as EW images). 122 | if (modestamp == 'IW' and productstamp == 'GRDH') or (modestamp == 'EW' and productstamp == 'GRDH'): 123 | down_tercorrected = do_terrain_correction(down_filtered, proj, 1) 124 | down_subset = do_subset(down_tercorrected, wkt) 125 | del down_filtered 126 | del down_tercorrected 127 | elif modestamp == 'EW' and productstamp == 'GRDM': 128 | tercorrected = do_terrain_correction(down_filtered, proj, 0) 129 | subset = do_subset(tercorrected, wkt) 130 | del down_filtered 131 | del tercorrected 132 | else: 133 | print("Different spatial resolution is found.") 134 | 135 | down = 1 136 | try: down_subset 137 | except NameError: 138 | down = None 139 | if down is None: 140 | print("Writing...") 141 | ProductIO.writeProduct(subset, outpath + '\\' + folder[:-5], 'GeoTIFF') 142 | del subset 143 | elif down == 1: 144 | print("Writing undersampled image...") 145 | ProductIO.writeProduct(down_subset, outpath + '\\' + folder[:-5] + '_40', 'GeoTIFF') 146 | del down_subset 147 | else: 148 | print("Error.") 149 | 150 | print('Done.') 151 | sentinel_1.dispose() 152 | sentinel_1.closeIO() 153 | print("--- %s seconds ---" % (time.time() - start_time)) 154 | 155 | if __name__== "__main__": 156 | main() 157 | --------------------------------------------------------------------------------