I am attributing an image collection with a value, I want to select the image with the median value of that image collection.
Currently I am doing this by creating a list of images with the value clientside, padding the list to be odd then using median to get the Id and value I want then filtering the collection by that ID. This works but I am having to make calls get values clientside to work out the median ID then filter the collection. I feel like this should be possible without having to make a clientside list.
So my question is can I do this without getting a client side list, all server side in EE, and how do I do this?
'''
# returns a list of image data scores
client_list = get_attribute_and_id_client_side_list(ts_collection, 'datascore')
# padding so we always get a value rather than an average of 2
if len(client_list) % 2 == 0:
client_list.append(['padding', 0])
median_datscore = np.median([item[1] for item in client_list])
median_score_coll = ts_collection.filterMetadata('data_score', 'equals', median_datscore)
'''
So essentially for a collection of images with a value in the metadata, I want to select just the image with the medain value without using any client side getInfo()
You can use the ImageCollection.reduceColumns
algorithm to obtain the median value of a property of a collection. Then you can use a filter to select the element with that value.
I've prepared an example in JavaScript, but since it is entirely server-side operations, it should not be too hard to translate:
var landsat = ee.ImageCollection("LANDSAT/LC08/C01/T1_TOA");
var pointOfInterest = ee.Geometry.Point([-115.10699177547879, 40.56521148064664]);
var images = landsat
.filterDate('2017-05-01', '2017-09-01')
.filterBounds(pointOfInterest)
.map(function(img) {
// Add a numeric property for the example: the value of B2 at a point.
return img.setMulti(img.select(['B2'], ['B2_at_point']).reduceRegion({
reducer: ee.Reducer.mean(),
geometry: pointOfInterest,
}));
});
// Display the list of values, just for the example (this is not used further).
print(images.reduceColumns(ee.Reducer.toList(), ['B2_at_point']).get('list'));
// Compute the median.
var median = ee.Number(
images.reduceColumns(ee.Reducer.median(), ['B2_at_point'])
.get('median'));
print(median);
// Find the image or images that had the median.
var exactlyEqualsMedian = images.filter(ee.Filter.equals('B2_at_point', median));
var equalsOrNextToMedian = ee.ImageCollection(ee.Algorithms.If(
exactlyEqualsMedian.first(), // Is exactlyEqualsMedian nonempty?
exactlyEqualsMedian, // Then return it.
images // Otherwise find the nearest 2 images.
.map(function (i) {
return i.set('median_distance', median.subtract(i.get('B2_at_point')).abs());
})
.limit(2, 'median_distance')));
print(equalsOrNextToMedian);
Map.setCenter(-115.08, 40.56, 9);
Map.addLayer(equalsOrNextToMedian, {bands: ['B4', 'B3', 'B2'], max: 0.4, gamma: 1.6});
https://code.earthengine.google.com/4e0d3d59535722f55425618e7defefff
(Please note that there is no scientific merit to the computation made here; it's just an example of finding a median in an image collection.)
Note that I did not take the approach of padding the input to median(). Rather, I filter afterward to pick either the exact match image or the two nearest matches. This is because I expect it will be more efficient than trying to set up padding beforehand (because it requires querying the collection an extra time to find out how large it is). You can of course choose whichever image in the resulting 2-image collection suits your needs — if you don't care which, just take equalsOrNextToMedian.first()
.