API Google Earth Engine Python: функция сопоставления коллекции изображений со списком каналов

Я использовал код Сэма Мерфи для атмосферной коррекции изображений Sentinel-2 в Google Earth Engine. Все идет хорошо и работает очень быстро для одного изображения. Я бы хотел сопоставить следующий код с коллекцией изображений:

output = image.select('QA60')
for band in ['B1','B2','B3','B4','B5','B6','B7','B8','B8A','B9','B10','B11','B12']:
    print(band)
    output = output.addBands(surface_reflectance(band))

Я думаю, мне нужна функция двойной карты для этого (чтобы не использовать здесь цикл for), но я еще не видел примеров Python в GEE.

Пока это то, что я придумал:

def atcorrector(image):
    qa = image.select('QA60')
    bands = ee.List(['B1','B2','B3','B4','B5','B6','B7','B8','B8A','B9','B10','B11','B12'])
    def mapper(bands):
        return qa.map(addBands(surface_reflectance(bands)))
    return qa

ImageCollection.map(atcorrector)

Однако это не возвращает изображение со всеми полосами, поэтому я чувствую, что моя вложенная функция не работает должным образом. Я новичок в Python, поэтому приветствую любую помощь!

Примечание: я уже экспериментировал с вторым репозиторием Сэма для атмосферной коррекции над коллекциями изображений. , но он выполняется слишком медленно, и я бы предпочел вычисление на стороне сервера с использованием предлагаемой функции карты, поскольку у меня есть множество изображений для обработки.

PS: Ниже приведен код функции surface_reflectance, извлеченный из репозитория Сэма Мерфи. Это требует одного из его индивидуальных занятий под названием «Атмосфера». Модель, используемая для атмосферной коррекции, - это модель Py6S.

# Package requirement
from Py6S import * 
import datetime
import math
import os
import sys
sys.path.append(os.path.join(os.path.dirname(os.getcwd()),'bin'))
from atmospheric import Atmospheric #Custom-defined class by Sam
import ee
ee.Initialize()

## The Sentinel-2 image collection
studyarea = ee.Geometry.Rectangle(7.8399,59.9273,8.2299,60.1208)#region of interest
S2 = ee.ImageCollection('COPERNICUS/S2').filterBounds(studyarea)\
       .filterDate('2016-06-01', '2016-06-10').sort('system:time_start')

## define metadata
info = S2one.getInfo()['properties']
scene_date = datetime.datetime.utcfromtimestamp(info['system:time_start']/1000)# i.e. Python uses seconds, EE uses milliseconds
solar_z = info['MEAN_SOLAR_ZENITH_ANGLE']
SRTM = ee.Image('USGS/GMTED2010')  # Make sure that your study area is covered by this elevation dataset
alt = SRTM.reduceRegion(reducer=ee.Reducer.mean(), geometry=studyarea.centroid()).get('be75').getInfo() # insert correct name for elevation variable from dataset
km = alt/1000  # i.e. Py6S uses units of kilometers

date = ee.Date(START_DATE)
# the following three variables are called on from the Atmospheric class Sam defined in his GitHub
h2o = Atmospheric.water(studyarea,date).getInfo() 
o3 = Atmospheric.ozone(studyarea,date).getInfo()
aot = Atmospheric.aerosol(studyarea,date).getInfo()

## Create the 6S Object
s = SixS() # Instantiate
# 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 (I think..)
s.geometry.solar_z = solar_z        # solar zenith angle
s.geometry.month = scene_date.month # month and day used for Earth-Sun distance
s.geometry.day = scene_date.day     # month and day used for Earth-Sun distance
s.altitudes.set_sensor_satellite_level()
s.altitudes.set_target_custom_altitude(km)

# Extract spectral response function for given band name
def spectralResponseFunction(bandname):
    bandSelect = {
        'B1':PredefinedWavelengths.S2A_MSI_01,
        'B2':PredefinedWavelengths.S2A_MSI_02,
        'B3':PredefinedWavelengths.S2A_MSI_03,
        'B4':PredefinedWavelengths.S2A_MSI_04,
        'B5':PredefinedWavelengths.S2A_MSI_05,
        'B6':PredefinedWavelengths.S2A_MSI_06,
        'B7':PredefinedWavelengths.S2A_MSI_07,
        'B8':PredefinedWavelengths.S2A_MSI_08,
        'B8A':PredefinedWavelengths.S2A_MSI_09,
        'B9':PredefinedWavelengths.S2A_MSI_10,
        'B10':PredefinedWavelengths.S2A_MSI_11,
        'B11':PredefinedWavelengths.S2A_MSI_12,
        'B12':PredefinedWavelengths.S2A_MSI_13,
        }
    return Wavelength(bandSelect[bandname])

# Converts top of atmosphere reflectance to at-sensor radiance
def toa_to_rad(bandname):
    ESUN = info['SOLAR_IRRADIANCE_'+bandname]
    solar_angle_correction = math.cos(math.radians(solar_z))# solar exoatmospheric spectral irradiance
    doy = scene_date.timetuple().tm_yday
    d = 1 - 0.01672 * math.cos(0.9856 * (doy-4))# Earth-Sun distance (from day of year)
    multiplier = ESUN*solar_angle_correction/(math.pi*d**2)# conversion factor
    rad = toa.select(bandname).multiply(multiplier)# at-sensor radiance
    return rad

# Calculate surface reflectance from at-sensor radiance given waveband name
def surface_reflectance(bandname):
    s.wavelength = spectralResponseFunction(bandname)  # run 6S for this waveband
    s.run()
    # extract 6S outputs
    Edir = s.outputs.direct_solar_irradiance             #direct solar irradiance
    Edif = s.outputs.diffuse_solar_irradiance            #diffuse solar irradiance
    Lp   = s.outputs.atmospheric_intrinsic_radiance      #path radiance
    absorb  = s.outputs.trans['global_gas'].upward       #absorption transmissivity
    scatter = s.outputs.trans['total_scattering'].upward #scattering transmissivity
    tau2 = absorb*scatter                                #total transmissivity
    # radiance to surface reflectance
    rad = toa_to_rad(bandname)
    ref = rad.subtract(Lp).multiply(math.pi).divide(tau2*(Edir+Edif))
    return ref

person Visithuru    schedule 13.02.2019    source источник
comment
Вы вообще не используете свою mapper функцию?   -  person Kevin    schedule 16.02.2019
comment
@Kevin да, я не уверен, как это исправить - не могли бы вы указать, что не так с синтаксисом? Мне нужно, чтобы моя функция atcorrector брала каждое изображение ee.ImageCollection и применяла функцию surface_reflectance к каждой полосе каждого изображения.   -  person Visithuru    schedule 16.02.2019


Ответы (1)


Чтобы применить функцию surface_reflectance по своему усмотрению, просто измените код следующим образом:

def atcorrector(image):
    qa = image.select('QA60')
    for band in ['B1','B2','B3','B4','B5','B6','B7','B8','B8A','B9','B10','B11','B12']:
        print(band)
        qa = qa.addBands(surface_reflectance(band))
    return qa

ImageCollection.map(atcorrector)

Как видите, этот код просто копирует код, который вы сделали для одного изображения. Цикл for работает без проблем в Python API (в JavaScript API я бы не рекомендовал его использовать). Если вы не хотите использовать цикл for, просто немного измените код:

def atcorrector(image):
    qa = image.select('QA60')
    bands = ee.List(['B1','B2','B3','B4','B5','B6','B7','B8','B8A','B9','B10','B11','B12'])

    def mapper(band):
        qa = qa.addBands(surface_reflectance(band))
        return band

    bands.map(mapper)
    return qa

ImageCollection.map(atcorrector)

Функция map объекта ee.List (или любого объекта ee, имеющего функцию map) может быть заменой цикла for.

Надеюсь это поможет.

person Kevin    schedule 18.02.2019
comment
Вот как я пытался это исправить - выкинул мне эту ошибку: UnboundLocalError: local variable 'qa' referenced before assignment Не имеет смысла, поскольку qa указано ранее в функции? Должно быть что-то более связанное с функцией surface_reflectance, которую я вызываю. Я подозреваю, что ему не нравится формат ee.List для названий групп, и он предпочитает обычный список. - person Visithuru; 19.02.2019
comment
Если вы используете Python 3.x, добавьте строку nonlocal qa сразу после строки def mapper (band):, чтобы предоставить функции mapper полный доступ к qa. Без этой строки функция mapper может читать qa, но не может его изменять. Речь идет об области видимости переменных в Python, а не о функции surface_reflectance. Как насчет подхода с использованием цикла for? - person Kevin; 19.02.2019
comment
Да! Добавление nonlocal qa избавило от ошибки. Подход с использованием цикла for работает, я его использовал, но он слишком медленный для моих требований. Я надеялся, что, сопоставив ee.List, я переключусь на вычисления на стороне сервера - не уверен, что это правильное предположение. Теперь, когда я запускаю его с этим правильным кодом, он говорит KeyError: <ee.computedobject.ComputedObject object at 0x7f19452d2400> - вероятно, необходимо настроить функцию surface_reflectance, чтобы она также принимала объекты ee.List. Есть идеи для этого перехода? Кстати, спасибо за ваше время! - person Visithuru; 19.02.2019
comment
Какой код у вашей surface_reflectance функции? - person Kevin; 19.02.2019
comment
Это довольно большой код. Я отредактировал вопрос, чтобы добавить его полностью, но, возможно, записная книжка исходного GitHub Будет легче читать. - person Visithuru; 19.02.2019