def test_6s_example1(self): # Implements Example_In_1.txt from the 6S distribution in Py6S # Tests against manual run of that file s = SixS() s.geometry = Geometry.User() s.geometry.solar_z = 40 s.geometry.solar_a = 100 s.geometry.view_z = 45 s.geometry.view_a = 50 s.geometry.month = 7 s.geometry.day = 23 s.atmos_profile = AtmosProfile.UserWaterAndOzone(3.0, 3.5) s.aero_profile = AeroProfile.User(dust=0.25, water=0.25, oceanic=0.25, soot=0.25) s.aot550 = 0.5 s.altitudes.set_target_custom_altitude(0.2) s.altitudes.set_sensor_custom_altitude(3.3, aot=0.25) s.wavelength = Wavelength(PredefinedWavelengths.AVHRR_NOAA9_B1) s.ground_reflectance = GroundReflectance.HeterogeneousLambertian( 0.5, GroundReflectance.ClearWater, GroundReflectance.GreenVegetation) s.atmos_corr = AtmosCorr.AtmosCorrBRDFFromReflectance(0.1) s.run() self.assertAlmostEqual(s.outputs.apparent_radiance, 12.749, delta=0.002)
def test_atmos_profile(self): aps = [ AtmosProfile.Tropical, AtmosProfile.NoGaseousAbsorption, AtmosProfile.UserWaterAndOzone(0.9, 3), ] results = [0.2723143, 0.2747224, 0.2476101] for i in range(len(aps)): s = SixS() s.atmos_profile = aps[i] s.run() self.assertAlmostEqual( s.outputs.apparent_reflectance, results[i], msg="Error in atmos profile with ID %s. Got %f, expected %f." % (str(aps[i]), s.outputs.apparent_reflectance, results[i]), delta=0.002, )
def test_6s_example4(self): s = SixS() s.geometry = Geometry.User() s.geometry.solar_z = 61.23 s.geometry.solar_a = 18.78 s.geometry.solar_a = 0 s.geometry.view_z = 18.78 s.geometry.view_a = 159.2 s.geometry.month = 4 s.geometry.day = 30 s.atmos_profile = AtmosProfile.UserWaterAndOzone(0.29, 0.41) s.aero_profile = AeroProfile.PredefinedType(AeroProfile.Continental) s.aot550 = 0.14 s.altitudes.set_target_sea_level() s.altitudes.set_sensor_satellite_level() s.wavelength = Wavelength(PredefinedWavelengths.AVHRR_NOAA11_B1) s.ground_reflectance = GroundReflectance.HomogeneousLambertian([ 0.827, 0.828, 0.828, 0.827, 0.827, 0.827, 0.827, 0.826, 0.826, 0.826, 0.826, 0.825, 0.826, 0.826, 0.827, 0.827, 0.827, 0.827, 0.828, 0.828, 0.828, 0.829, 0.829, 0.828, 0.826, 0.826, 0.825, 0.826, 0.826, 0.826, 0.827, 0.827, 0.827, 0.826, 0.825, 0.826, 0.828, 0.829, 0.830, 0.831, 0.833, 0.834, 0.835, 0.836, 0.836, 0.837, 0.838, 0.838, 0.837, 0.836, 0.837, 0.837, 0.837, 0.840, 0.839, 0.840, 0.840, 0.841, 0.841, 0.841, 0.841, 0.842, 0.842, 0.842, 0.842, 0.843, 0.842, 0.843, 0.843, 0.843, 0.843, 0.843, 0.843, 0.841, 0.841, 0.842, 0.842, 0.842, 0.842, 0.842, 0.841, 0.840, 0.841, 0.838, 0.839, 0.837, 0.837, 0.836, 0.832, 0.832, 0.830, 0.829, 0.826, 0.826, 0.824, 0.821, 0.821, 0.818, 0.815, 0.813, 0.812, 0.811, 0.810, 0.808, 0.807, 0.807, 0.812, 0.808, 0.806, 0.807, 0.807, 0.807, 0.807, ]) s.run() self.assertAlmostEqual(s.outputs.apparent_radiance, 170.771, delta=0.002)
def run_py6s(self, geom, acqui_date, option=1): """ runs the 6S algorithm for atmospheric correction on a user-defined geometry and date on Sentinel-2 imagery Requires Google Earth-Engine Python API client Parameters ---------- geom : EE-Geometry Google EE geometry specify the geographic extent to be processed acqui_date : String date (YYYY-MM-dd) of the desired scene to be processed option : Integer for computing the cloud mask the user can decide whether the ESA delivered quality layer should be used for cloud masking (option=1) or an alternative approach originally developed for Landsat TM (option=2) should be used (Def=1) Returns ------- s2_surf : ee.image.Image Google EE image instance with surface reflectance values and two additional bands containing Clouds ('CloudMask') and detected cloud shadows ('CloudShadows') """ date = ee.Date(acqui_date) # get the Sentinel-2 image at or immediately after the specified date self.S2 = ee.Image( ee.ImageCollection('COPERNICUS/S2') .filterBounds(geom) .filterDate(date,date.advance(3,'month')) .sort('system:time_start') .first() ) # extract the relevant metadata for carrying out the atmospheric correction self.info = self.S2.getInfo()['properties'] # get the solar zenith angle an the scene data self.scene_date = datetime.datetime.utcfromtimestamp( self.info['system:time_start']/1000) self.solar_z = self.info['MEAN_SOLAR_ZENITH_ANGLE'] # log the identifier of the processed scene scene_id = self.info.get('DATASTRIP_ID') self.__logger.info("Starting Processing scene '{}' using GEE and Py6S".format( scene_id)) # get the atmospheric constituents # i.e water vapor (h2o), ozone (o3), aerosol optical thickness (aot) h2o = Atmospheric.water(geom, date).getInfo() o3 = Atmospheric.ozone(geom, date).getInfo() aot = Atmospheric.aerosol(geom, date).getInfo() # add this information to the info dictionary as this metadata could # be of interest afterwards self.info['WATER_VAPOUR'] = h2o self.info['OZONE'] = o3 self.info['AOT'] = aot # get the average altitude of the region to be processed # for the Digital Elevation Model (DEM) the Shuttle Radar Topography # Mission (SRTM) is used (Version 4) as it covers most parts of the # Earth (90 arc-sec data is used) SRTM = ee.Image('CGIAR/SRTM90_V4') alt = SRTM.reduceRegion(reducer = ee.Reducer.mean(), geometry = geom.centroid()).get('elevation').getInfo() message = "Atcorr-Metadata: Water-Vapor = {0}, Ozone = {1}, AOT = {2}, "\ " Average Altitude (m) = {3}".format( h2o, o3, aot, alt) self.__logger.info(message) # Py6S uses units of kilometers km = alt/1000 # mask out clouds and cirrus from the imagery using the cloud score # optionally # algorithm provided by Sam Murhpy under Apache 2.0 licence # see: https://github.com/samsammurphy/cloud-masking-sentinel2/blob/master/cloud-masking-sentinel2.ipynb # also converts the image values to top-of-atmosphere reflectance self.__logger.info('Calculating cloud and shadow mask') self.S2 = mask_clouds(self.S2, option=1) self.__logger.info('Finished calculating cloud and shadow mask') # create a 6S object from the Py6S class # Instantiate (use the explizit path to installation directory of the # 6S binary as otherwise there might be an error) s = SixS() # Atmospheric constituents s.atmos_profile = AtmosProfile.UserWaterAndOzone(h2o,o3) s.aero_profile = AeroProfile.Continental s.aot550 = aot # Earth-Sun-satellite geometry s.geometry = Geometry.User() s.geometry.view_z = 0 # always NADIR (simplification!) s.geometry.solar_z = self.solar_z # solar zenith angle s.geometry.month = self.scene_date.month # month and day used for Earth-Sun distance s.geometry.day = self.scene_date.day # month and day used for Earth-Sun distance s.altitudes.set_sensor_satellite_level() s.altitudes.set_target_custom_altitude(km) self.__logger.info('6S: Starting processing of Sentinel-2 scene!') # now iterate over the nine relevant Sentinel-2 bands to perform the # atmospheric correction and get the surface reflectance # go through the spectral bands B2_surf = self.surface_reflectance(s, 'B2') self.__logger.info('6S: Finished processing Sentinel-2 Band 2!') B3_surf = self.surface_reflectance(s, 'B3') self.__logger.info('6S: Finished processing Sentinel-2 Band 3!') B4_surf = self.surface_reflectance(s, 'B4') self.__logger.info('6S: Finished processing Sentinel-2 Band 4!') B5_surf = self.surface_reflectance(s, 'B5') self.__logger.info('6S: Finished processing Sentinel-2 Band 5!') B6_surf = self.surface_reflectance(s, 'B6') self.__logger.info('6S: Finished processing Sentinel-2 Band 6!') B7_surf = self.surface_reflectance(s, 'B7') self.__logger.info('6S: Finished processing Sentinel-2 Band 7!') B8A_surf = self.surface_reflectance(s, 'B8A') self.__logger.info('6S: Finished processing Sentinel-2 Band 8A!') B11_surf = self.surface_reflectance(s, 'B11') self.__logger.info('6S: Finished processing Sentinel-2 Band 11!') B12_surf = self.surface_reflectance(s, 'B12') self.__logger.info('6S: Finished processing Sentinel-2 Band 12!') self.__logger.info('6S: Finished processing of Sentinel-2 scene!') # make a stack of the spectral bands # also add the computed cloud and shadow mask cm = self.S2.select('CloudMask') sm = self.S2.select('ShadowMask') S2_surf = B2_surf.addBands(B3_surf).addBands(B4_surf).addBands(B5_surf).addBands(B6_surf).addBands(B7_surf).addBands(B8A_surf).addBands(B11_surf).addBands(B12_surf).addBands(cm).addBands(sm) # return the surface reflectance image close_logger(self.__logger) return S2_surf