Satellite Imagery Analysis with Python. II

Daniel Moraite
DataSeries
4 min readMay 28, 2019

--

eo-learn

I guess I have a thing for keeping up with the newest libraries and repositories for manipulating data from more and more data-intensive sources. Of course, I wouldn’t have had introduced you to them unless they aide and facilitate the application of machine learning to solve problems and find innovative solutions:

  • efficiently handle big data — handle different data sources and formats — complete workflows
  • handle spatio-temporal data — prototype, build and automate — deal with lack of training labels
  • create and validate machine learning applications — deal with missing data

Today, I would like to introduce you to an upcoming amazing library: eo-learn.

eo-learn not only makes extraction of valuable information from satellite imagery easy, is has as well a very high goal: to democratize Earth observation big data. It started as a response to the availability of open Earth observation (EO) data through the Copernicus and Landsat programs. And to quote:

is a collection of open source Python packages that have been developed to seamlessly access and process spatio-temporal image sequences acquired by any satellite fleet in a timely and automatic manner.

eo-learn library acts as a bridge between Earth observation/Remote sensing field and Python ecosystem for data science and machine learning.

In this post will have a look at the basics of using eo-learn and give it a go to downloading, saving and visualizing EO imaging data(RGB, NDVI, Scene Classification masks, digital elevation).

Setup

  • install Sentinel Hub
  • install eo-learn

(please find bellow, under resources, the links for the above)

Data Extraction

You’ll find details of how to get your area of interest AOI coordinates in my previous: Satellite Imagery Analysis with Python post. Just make sure you select from the menu: meta, respectively add bboxes.

Define ROI BBOX and time interval
I have picked an area around my home town, a beautiful agricultural area around Danube River.

roi_bbox = BBox(bbox=[27.67, 44.97, 28.03, 45.26], crs=CRS.WGS84)
time_interval = ('2019-04-01', '2019-05-01')

Request different types of layers and data sources to an eopatch

layer = 'BANDS-S2-L1C'
input_task = S2L1CWCSInput(layer=layer,
resx='20m', resy='20m',
maxcc=.3, time_difference=datetime.timedelta(hours=2))
add_ndvi = S2L1CWCSInput(layer='NDVI')
add_dem = DEMWCSInput(layer='DEM')
add_l2a = S2L2AWCSInput(layer='BANDS-S2-L2A')
add_sen2cor = AddSen2CorClassificationFeature('SCL', layer='BANDS-S2-L2A')
save = SaveToDisk('io_example', overwrite_permission=2, compress_level=1)

Sentinel2 L1C and L2A bands are requested (all of them 12–13bands) at 20m resolution, maxcc= max cloud coverage(you can play with it in between 0.8 and 0.05), and NDVI, DEM digital elevation model.

Run workflow

workflow = LinearWorkflow(input_task, add_ndvi, add_l2a, add_sen2cor, add_dem, save)
result = workflow.execute({input_task: {'bbox': roi_bbox, 'time_interval': time_interval},
save: {'eopatch_folder': 'eopatch'}})

Check contents of eopatch

eopatch = result[save]
eopatch

EOPatch(
data: {
BANDS-S2-L1C: numpy.ndarray(shape=(5, 1613, 1413, 13), dtype=float32)
BANDS-S2-L2A: numpy.ndarray(shape=(5, 1613, 1413, 12), dtype=float32)
NDVI: numpy.ndarray(shape=(5, 1613, 1413, 1), dtype=float32)
}
mask: {
IS_DATA: numpy.ndarray(shape=(5, 1613, 1413, 1), dtype=bool)
SCL: numpy.ndarray(shape=(5, 1613, 1413, 1), dtype=int32)
}
scalar: {}
label: {}
vector: {}
data_timeless: {
DEM: numpy.ndarray(shape=(1613, 1413, 1), dtype=float32)
}
mask_timeless: {}
scalar_timeless: {}
label_timeless: {}
vector_timeless: {}
meta_info: {
maxcc: 0.3
service_type: 'wcs'
size_x: '20m'
size_y: '20m'
time_difference: datetime.timedelta(0, 7200)
time_interval: ('2019-04-01', '2019-05-01')
}
bbox: BBox(((27.67, 44.97), (28.03, 45.26)), crs=EPSG:4326)
timestamp: [datetime.datetime(2019, 4, 1, 9, 18, 16), ..., datetime.datetime(2019, 4, 28, 9, 8, 8)], length=5
)

Plot results

S2 L1C RGB bands

plt.figure(figsize=(10,10))
plt.imshow(eopatch.data['BANDS-S2-L1C'][3][..., [3,2,1]] * 2.5, vmin=0, vmax=1); ![# Welcome to my adventure](/images/BR1S2L1CRGBbands.png)

NDVI dervied from S2 L1C bands

plt.figure(figsize=(10,10))
plt.imshow(eopatch.data['NDVI'][3].squeeze()); ![# Welcome to my adventure](/images/BR2NDVIderviedfromS2L1Cbands.png)

S2 L2A RGB bands

plt.figure(figsize=(10,10))
plt.imshow(eopatch.data['BANDS-S2-L2A'][3][...,[3,2,1]] * 2.5, vmin=0, vmax=1); ![# Welcome to my adventure](/images/BR3S2L2ARGBbands.png)

Sen2cor scene classification mask

plt.figure(figsize=(10,10))
plt.imshow(eopatch.mask['SCL'][3].squeeze()); ![# Welcome to my adventure](/images/BR4Sen2corsceneclassificationmask.png)

Mapzen Digital Elevation Model

plt.figure(figsize=(10, 10))
plt.imshow(eopatch.data_timeless['DEM'].squeeze()); ![# Welcome to my adventure](/images/BR5MapzenDigitalElevationModel.png)

Load in saved eopatch

load = LoadFromDisk('io_example')
new_eopatch = load.execute(eopatch_folder='eopatch')

Thoughts: if you compare my first article on extracting data from a satellite, and then calculate the NDVI and so on.. you’ll find that eo-learn can actually help you save tons of time, skipping through a few procedures. Plus you got all the eopatches saved for further processing with machine learning. Well done!

Please find the entire code here

Feel free to pick you own coordinates. You might play with the time frame as well..

Resources:

Notes:

# SentinelHub API Key (instance id) stored as an env variable(if you want to call it from your notebook):        
import os
os.environ['INSTANCE_ID']='your instance ID'
INSTANCE_ID = os.environ.get('INSTANCE_ID')

HAVE FUN!

--

--

Daniel Moraite
DataSeries

My passion for technology and previous roles inspired me to get closer to the practical side of things and I started studying data science and coding on my own.