Trasformazioni di array

Earth Engine supporta le trasformazioni degli array, come la trasposizione, l'inverso e lo pseudo-inverso. Ad esempio, prendiamo in considerazione una regressione con minimi quadrati ordinari (OLS) di una serie temporale di immagini. Nell'esempio seguente, un'immagine con bande per gli indicatori e una risposta viene convertita in un'immagine array, quindi "risoluta" per ottenere le stime dei coefficienti minimi quadratici in tre modi. Innanzitutto, assembla i dati dell'immagine e convertili in array:

Editor di codice (JavaScript)

// Scales and masks Landsat 8 surface reflectance images.
function prepSrL8(image) {
  // Develop masks for unwanted pixels (fill, cloud, cloud shadow).
  var qaMask = image.select('QA_PIXEL').bitwiseAnd(parseInt('11111', 2)).eq(0);
  var saturationMask = image.select('QA_RADSAT').eq(0);

  // Apply the scaling factors to the appropriate bands.
  var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
  var thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0);

  // Replace the original bands with the scaled ones and apply the masks.
  return image.addBands(opticalBands, null, true)
      .addBands(thermalBands, null, true)
      .updateMask(qaMask)
      .updateMask(saturationMask);
}

// Load a Landsat 8 surface reflectance image collection.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
  // Filter to get only two years of data.
  .filterDate('2019-04-01', '2021-04-01')
  // Filter to get only imagery at a point of interest.
  .filterBounds(ee.Geometry.Point(-122.08709, 36.9732))
  // Prepare images by mapping the prepSrL8 function over the collection.
  .map(prepSrL8)
  // Select NIR and red bands only.
  .select(['SR_B5', 'SR_B4'])
  // Sort the collection in chronological order.
  .sort('system:time_start', true);

// This function computes the predictors and the response from the input.
var makeVariables = function(image) {
  // Compute time of the image in fractional years relative to the Epoch.
  var year = ee.Image(image.date().difference(ee.Date('1970-01-01'), 'year'));
  // Compute the season in radians, one cycle per year.
  var season = year.multiply(2 * Math.PI);
  // Return an image of the predictors followed by the response.
  return image.select()
    .addBands(ee.Image(1))                                  // 0. constant
    .addBands(year.rename('t'))                             // 1. linear trend
    .addBands(season.sin().rename('sin'))                   // 2. seasonal
    .addBands(season.cos().rename('cos'))                   // 3. seasonal
    .addBands(image.normalizedDifference().rename('NDVI'))  // 4. response
    .toFloat();
};

// Define the axes of variation in the collection array.
var imageAxis = 0;
var bandAxis = 1;

// Convert the collection to an array.
var array = collection.map(makeVariables).toArray();

// Check the length of the image axis (number of images).
var arrayLength = array.arrayLength(imageAxis);
// Update the mask to ensure that the number of images is greater than or
// equal to the number of predictors (the linear model is solvable).
array = array.updateMask(arrayLength.gt(4));

// Get slices of the array according to positions along the band axis.
var predictors = array.arraySlice(bandAxis, 0, 4);
var response = array.arraySlice(bandAxis, 4);

Configurazione di Python

Per informazioni sull'API Python e sull'utilizzo di geemap per lo sviluppo interattivo, consulta la pagina Ambiente Python.

import ee
import geemap.core as geemap

Colab (Python)

import math


# Scales and masks Landsat 8 surface reflectance images.
def prep_sr_l8(image):
  # Develop masks for unwanted pixels (fill, cloud, cloud shadow).
  qa_mask = image.select('QA_PIXEL').bitwiseAnd(int('11111', 2)).eq(0)
  saturation_mask = image.select('QA_RADSAT').eq(0)

  # Apply the scaling factors to the appropriate bands.
  optical_bands = image.select('SR_B.').multiply(0.0000275).add(-0.2)
  thermal_bands = image.select('ST_B.*').multiply(0.00341802).add(149.0)

  # Replace the original bands with the scaled ones and apply the masks.
  return (
      image.addBands(optical_bands, None, True)
      .addBands(thermal_bands, None, True)
      .updateMask(qa_mask)
      .updateMask(saturation_mask)
  )


# Load a Landsat 8 surface reflectance image collection.
collection = (
    ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
    # Filter to get only two years of data.
    .filterDate('2019-04-01', '2021-04-01')
    # Filter to get only imagery at a point of interest.
    .filterBounds(ee.Geometry.Point(-122.08709, 36.9732))
    # Prepare images by mapping the prep_sr_l8 function over the collection.
    .map(prep_sr_l8)
    # Select NIR and red bands only.
    .select(['SR_B5', 'SR_B4'])
    # Sort the collection in chronological order.
    .sort('system:time_start', True)
)


# This function computes the predictors and the response from the input.
def make_variables(image):
  # Compute time of the image in fractional years relative to the Epoch.
  year = ee.Image(image.date().difference(ee.Date('1970-01-01'), 'year'))
  # Compute the season in radians, one cycle per year.
  season = year.multiply(2 * math.pi)
  # Return an image of the predictors followed by the response.
  return (
      image.select()
      .addBands(ee.Image(1))  # 0. constant
      .addBands(year.rename('t'))  # 1. linear trend
      .addBands(season.sin().rename('sin'))  # 2. seasonal
      .addBands(season.cos().rename('cos'))  # 3. seasonal
      .addBands(image.normalizedDifference().rename('NDVI'))  # 4. response
      .toFloat()
  )


# Define the axes of variation in the collection array.
image_axis = 0
band_axis = 1

# Convert the collection to an array.
array = collection.map(make_variables).toArray()

# Check the length of the image axis (number of images).
array_length = array.arrayLength(image_axis)
# Update the mask to ensure that the number of images is greater than or
# equal to the number of predictors (the linear model is solvable).
array = array.updateMask(array_length.gt(4))

# Get slices of the array according to positions along the band axis.
predictors = array.arraySlice(band_axis, 0, 4)
response = array.arraySlice(band_axis, 4)

Tieni presente che arraySlice() restituisce tutte le immagini nella serie temporale per l'intervallo di indici specificato lungo bandAxis (l'asse 1). A questo punto, è possibile utilizzare l'algebra matriciale per risolvere i coefficienti OLS:

Editor di codice (JavaScript)

// Compute coefficients the hard way.
var coefficients1 = predictors.arrayTranspose().matrixMultiply(predictors)
  .matrixInverse().matrixMultiply(predictors.arrayTranspose())
    .matrixMultiply(response);

Configurazione di Python

Per informazioni sull'API Python e sull'utilizzo di geemap per lo sviluppo interattivo, consulta la pagina Ambiente Python.

import ee
import geemap.core as geemap

Colab (Python)

# Compute coefficients the hard way.
coefficients_1 = (
    predictors.arrayTranspose()
    .matrixMultiply(predictors)
    .matrixInverse()
    .matrixMultiply(predictors.arrayTranspose())
    .matrixMultiply(response)
)

Anche se questo metodo funziona, non è efficiente e rende difficile la lettura del codice. Un modo migliore è utilizzare il metodo pseudoInverse() (matrixPseudoInverse() per un'immagine array):

Editor di codice (JavaScript)

// Compute coefficients the easy way.
var coefficients2 = predictors.matrixPseudoInverse()
  .matrixMultiply(response);

Configurazione di Python

Per informazioni sull'API Python e sull'utilizzo di geemap per lo sviluppo interattivo, consulta la pagina Ambiente Python.

import ee
import geemap.core as geemap

Colab (Python)

# Compute coefficients the easy way.
coefficients_2 = predictors.matrixPseudoInverse().matrixMultiply(response)

Dal punto di vista della leggibilità e dell'efficienza di calcolo, il modo migliore per ottenere i coefficienti OLS è solve() (matrixSolve() per un'immagine array). La funzione solve() determina come risolvere al meglio il sistema dalle caratteristiche degli input, utilizzando l'operatore pseudo-inverso per i sistemi sovradeterminati, l'operatore inverso per le matrici quadrate e tecniche speciali per le matrici quasi singolari:

Editor di codice (JavaScript)

// Compute coefficients the easiest way.
var coefficients3 = predictors.matrixSolve(response);

Configurazione di Python

Per informazioni sull'API Python e sull'utilizzo di geemap per lo sviluppo interattivo, consulta la pagina Ambiente Python.

import ee
import geemap.core as geemap

Colab (Python)

# Compute coefficients the easiest way.
coefficients_3 = predictors.matrixSolve(response)

Per ottenere un'immagine multibanda, proietta l'immagine dell'array in uno spazio di dimensioni inferiori, quindi appiattiscila:

Editor di codice (JavaScript)

// Turn the results into a multi-band image.
var coefficientsImage = coefficients3
  // Get rid of the extra dimensions.
  .arrayProject([0])
  .arrayFlatten([
    ['constant', 'trend', 'sin', 'cos']
]);

Configurazione di Python

Per informazioni sull'API Python e sull'utilizzo di geemap per lo sviluppo interattivo, consulta la pagina Ambiente Python.

import ee
import geemap.core as geemap

Colab (Python)

# Turn the results into a multi-band image.
coefficients_image = (
    coefficients_3
    # Get rid of the extra dimensions.
    .arrayProject([0]).arrayFlatten([['constant', 'trend', 'sin', 'cos']])
)

Esamina gli output dei tre metodi e osserva che la matrice risultante dei coefficienti è la stessa indipendentemente dal risolutore. Il fatto che solve() sia flessibile ed efficiente lo rende una buona scelta per la creazione di modelli lineari generici.