2

Does anyone know if there is a more GEE-friendly way of achieving this? I'm experimenting with S1 SAR image histogram matching in GEE and was wondering if foremost the code can be improved. Read here that the use of for-loops and getInfo() calls should be discouraged due to the overhead it brings with transferring data downstream to the browser and upstream to the server.

Here's the code:

/**
* Function that returns a normalized image using unitScale()
* @param image the image object
* @param band the band to select
* @param aoi the area of interest for scoping the reducer
*/
function getNormalizedImage(image, band, aoi) {
  var redux = getReducedImage(image, ee.Reducer.minMax(), aoi);
  return image.unitScale(
    ee.Number(redux.get(band + '_min')),
    ee.Number(redux.get(band + '_max'))
  );
}

/**
 * Function wrapper for a reducer
 * @param image the image object
 * @param reducer the reducer to apply, see API docs
 * @param aoi the area of interest for scoping the reducer
 */
function getReducedImage(image, reducer, aoi) {
  return image.reduceRegion({
    reducer: reducer,
    geometry: aoi,
    scale: 10,
    bestEffort: true
  });
}

/**
 * Function that returns a histogram array object
 * @param image the image object
 * @param band the band to select
 * @param aoi the area of interest for scoping the reducer
 */
function getHistogram(image, band, aoi) {
  var redux = image.reduceRegion({
    reducer: ee.Reducer.histogram(256, 1),
    geometry: aoi,
    scale: 10,
    bestEffort: true
  });
  var histoDict = ee.Dictionary(redux.get(band)).get('histogram');
  return histoDict;
}

/**
 * Function that returns a cumulative distribution function 
 * as a list object
 * @param image the image object
 * @param band the band to select
 * @param aoi the area of interest for scoping the reducer
 */
function getCDF(image, band, aoi) {
  var histoArr = ee.List(getHistogram(image.multiply(255), band, aoi));
  var len = histoArr.length().subtract(1);
  var nPixels = ee.Array(histoArr).accum(0).toList().get(len);
  var CDF = ee.Array(histoArr).accum(0).divide(nPixels).multiply(len);
  return [CDF.round().toList(), len];
}

/**
 * Function that returns a source image that has been 
 * matched with the target's image histogram using
 * the remap function from Earth Engine
 * @param imageR the source image object
 * @param imageZ the target image object
 * @param bandR the source band to select
 * @param bandZ the target band to select
 * @param aoi the area of interest for scoping the reducer
 */
function matchHistogram(imageR, imageZ, bandR, bandZ, aoi) {
  var cdfR = getCDF(imageR, bandR, aoi);
  var cdfZ = getCDF(imageZ, bandZ, aoi);
  var lenR = cdfR[1];
  var lenZ = cdfZ[1];
  var map = [];

  var cdfR0 = cdfR[0].getInfo();
  var cdfZ0 = cdfZ[0].getInfo();

  for (var r = 0; r < cdfR0.length; r++) {
    var max = Infinity;
    var min = 0;
    for (var z = 0; z < cdfZ0.length; z++) {
      var diff = Math.pow(cdfZ0[z] - cdfR0[r], 2);
      if (diff < max) {
        max = diff;
        min = z;
      }
    }
    map.push(min);
  }

  var seq = ee.List.sequence(0, lenR);
  var imageM = imageR.multiply(lenR).round().remap(seq, map, 0).divide(lenR);
  return imageM.select(['remapped'], [bandR]);
}

getInfo() is being called in the functions matchHistogram and getCDF.

UPDATE: I've managed to improve getCDF by removing the getInfo() call there, matchHistogram seems a bit more tricky to solve.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.