Main features#

These features can be seen in the basic tutorial.


The reader singleton is your unique entry. It will create for you the product object corresponding to your satellite data.

You can load products from the cloud, see this tutorial. S3 and S3 Compatible Storage are working and maybe Google and Azure if rasterio supports it, but they have not been tested.

>>> import os
>>> from reader import Reader

>>> # Path to your satellite data, ie. Sentinel-2
>>> path = r''  # You can work with the archive for S2 data

>>> # Path to your output directory (if not set, it will work in a temp directory)
>>> output = os.path.abspath('.')

>>> # Create the reader singleton
>>> reader = Reader()
>>> prod =, output_path=output, remove_tmp=True)
>>> # remove_tmp allows you to automatically delete processing files 
>>> # such as cleaned or orthorectified bands when the product is deleted
>>> # False by default to speed up the computation if you want to use the same product in several part of your code

>>> # NOTE: you can set the output directory after the creation, that allows you to use the product condensed name
>>> prod.output = os.path.join(output, prod.condensed_name)  # It will automatically create it if needed

Recognized paths#

EOReader always uses the directory containing the product files. Hereunder are the paths meant to be given to the reader.


Sensor group

Folder to link

Sentinel-2 and 3

Main directory, .SAFE, .SEN3 or .zip,
i.e. S2A_MSIL1C_20200824T110631_N0209_R137_T30TTK_20200824T150432.SAFE

Sentinel-2 Theia

Main directory containing the .tif images,
i.e. SENTINEL2A_20190625-105728-756_L2A_T31UEQ_C_V2-2


Main directory extracted or archived if Collection 2 (.tar),
i.e. LC08_L1TP_200030_20201220_20210310_02_T1.tar


Directory containing the manifest.json file,
i.e. 20210406_015904_37_2407

(Pleiades, SPOTs,
Vision-1, …)

Directory containing the .JP2 files,
i.e. IMG_PHR1B_PMS_001


Directory containing the .TIL file,
i.e. 013187549010_01_P001_PSH


Sensor group

Folder to link


SAFE directory containing the file,
i.e. S1A_IW_GRDH_1SDV_20191215T060906_20191215T060931_030355_0378F7_3696.SAFE

1st and 2nd Gen

Directory containing the .h5 image,
i.e. 1011117-766193


Main directory containing the .tif image,


Directory containing the IMAGEDATA directory,
i.e. TDX1_SAR__MGD_SE___SM_S_SRA_20201016T231611_20201016T231616


Directory containing the .tif file,
i.e. SC_124020


Directory containing the .xemt AND the .zip files,
i.e. 11245-EOL1CSARSAO1A198523


load is the function for accessing product-related bands. It can load satellite bands, index, DEM bands and cloud bands according to this workflow: load_workflow

>>> import os
>>> from eoreader.reader import Reader
>>> from eoreader.bands import *
>>> import os
>>> from eoreader.env_vars import DEM_PATH

>>> path = r"S2B_MSIL1C_20210517T103619_N7990_R008_T30QVE_20210929T075738.SAFE"
>>> output = os.path.abspath("./output")
>>> # WARNING: you can leave the output_path empty, but EOReader will create a temporary output directory
>>> # and you won't be able to retrieve what's has been written on disk
>>> prod = Reader().open(path, output_path=output)

>>> # Specify a DEM to load DEM bands
>>> os.environ[DEM_PATH] = r"my_dem.tif"

>>> # Get the wanted bands and check if the product can produce them
>>> band_list = [GREEN, NDVI, TIR_1, SHADOWS, HILLSHADE]
>>> ok_bands = to_str([band for band in band_list if prod.has_band(band)])
>>>  # Sentinel-2 cannot produce satellite band TIR_1 and cloud band SHADOWS

>>>  # Load bands
>>> bands = prod.load(ok_bands, resolution=20.)  # if resolution is not specified -> load at default resolution (10.0 m for S2 data)
>>>  # NOTE: every array that comes out `load` are collocated, which isn't the case if you load arrays separately
>>>  # (important for DEM data as they may have different grids)


Index and bands are opened as xarrays with rioxarray, in float with the nodata set to np.nan. The nodata written back on disk is by convention:

  • -9999 for optical bands (saved in float32)

  • 65535 for optical bands (saved in uint16)

  • 0 for SAR bands (saved in float32), to be compliant with SNAP default nodata

  • 255 for masks (saved in uint8)

For optical bands, only the pixels outside of the detector are set to nodata by default but this can be changed according to the user’s needs (see below).

Some additional arguments can be passed to this function, please see keywords for the list.

  • Methods to clean optical bands are best described here,

  • Sentinel-3 additional keywords use is highlighted in the corresponding notebook.


stack is the function stacking all possible bands. It is based on the load function and then just stacks the bands and write it on disk if needed.

The bands are ordered as asked in the stack. However, they cannot be duplicated (the stack cannot contain 2 RED bands for instance)! If the same band is asked several time, its order will be the one of the last demand.

>>> # Create a stack with the previous OK bands
>>> stack = prod.stack(ok_bands, resolution=300., stack_path=os.path.join(prod.output, "stack.tif")

Some additional arguments can be passed to this function, please see keywords for the list.

  • Methods to clean optical bands are best described here,

  • Sentinel-3 additional keywords use is highlighted in the corresponding notebook.

Read Metadata#

EOReader gives you the access to the metadata of your product as a lxml.etree._Element followed by the namespace you may need to read them

>>> # Access the raw metadata as an lxml.etree._Element and its namespaces as a dict:
>>> mtd, nmsp = prod.read_mtd()

>>> # You can access a field like that: 
>>> datastrip_id = mtd.findtext(".//DATASTRIP_ID")

>>> # Pay attention, for some products you will need a namespace, i.e. for planet data:
>>> # name = mtd.findtext(f".//{nsmap['eop']}identifier")


Landsat Collection 1 have no metadata with XML format, so the XML is simulated from the text file.


Sentinel-3 constellations have no metadata file but have global attributes repeated in every NetCDF files. This is what you will have when calling this function:

  • absolute_orbit_number

  • comment

  • contact

  • creation_time

  • history

  • institution

  • netCDF_version

  • product_name

  • references

  • resolution

  • source

  • start_offset

  • start_time

  • stop_time

  • title

  • ac_subsampling_factor (OLCI only)

  • al_subsampling_factor (OLCI only)

  • track_offset (SLSTR only)


If a quicklook exists, the user can plot the product. Always existing for VHR and SAR data, more rarely for other optical constellations. See Optical and SAR tutorials for examples.

>>> # Plot product
>>> prod.plot()

Other features#


Get the product CRS, always in UTM

>>> # Product CRS (always in UTM)

Extent and footprint#

Get the product extent and footprint, always in UTM as a gpd.GeoDataFrame

>>> # Full extent of the bands as a geopandas GeoDataFrame
>>> prod.extent()
0   POLYGON((309780.000 4390200.000, 309780.000 4...

>>> # Footprint: extent of the useful pixels (minus nodata) as a geopandas GeoDataFrame
>>> prod.footprint()
0 POLYGON Z((199980.000 4390200.000 0.000, 1999...

Please note the difference between footprint and extent:

Without nodata

With nodata



Solar angles#

Get optical product azimuth (between [0, 360] degrees) and zenith solar angles, useful for computing the Hillshade for example.

>>>  # Get azimuth and zenith solar angles
>>> prod.get_mean_sun_angles()
(81.0906721240477, 17.5902388851456)

Cloud Cover#

Get optical product cloud cover as specified in the metadata

>>>  # Get cloud cover
>>> prod.get_cloud_cover()

Orbit direction#

Get product optical direction (useful especially for SAR data), as a OrbitDirection (ASCENDING or DESCENDING). Always specified in the metadata for SAR constellations, set to DESCENDING by default for optical data if not existing.

>>>  # Get orbit direction
>>> prod.get_orbit_direction()


EOReader can help you create SpatioTemporal Asset Catalog (STAC) items from every supported products, included custom ones. Those items are ready to be added in any STAC catalogue or collection. See STAC Notebook to learn more about this feature.

>>>  # Get STAC object
>>> prod.stac

>>>  # Create STAC item
>>> prod.stac.create_item()
<Item id = 20210517T103619_S2_T30QVE_L1C_075738>

Some functions have different names between EOReader and STAC vocabulary. For legacy purpose, this has not been changed. Hereunder is the mapping:

  • prod.stac.bbox is equivalent to prod.extent but in WGS84 (EPSG:4326)

  • prod.stac.proj.bbox is equivalent to prod.extent

  • prod.stac.geometry is equivalent to prod.footprint but in WGS84 (EPSG:4326)

  • prod.stac.proj.geometry is equivalent to prod.footprint