Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

It is possible to use the SNAP Java API from Python and there are basically in principle two different ways to achieve this:

  1. Use an a standard Python (CPython) installation

...

  1. Use the Jython approach. Note: For the most recent versions of SNAP we discourage the usage of Jython. As of SNAP 8 the Jython support is not part of the SNAP standard distribution anymore. 

With the recommended standard Python (CPython) approach, it is possible to call SNAP code from your Python programs/scripts and to extend SNAP by plugins written in Python. However, the two ways have pros and cons which should be considered before you make a decision which you want to follow.Use the standard Python (CPython) approach, Use this approach if

  • you require using the Python scientific extension libraries such as numpy, scipy, matplotlib, etc.;
  • you already have CPython code and you want to incorporate SNAP functions;
  • you plan to implement a fast data processor plugin in Python;
  • you do not plan to develop SNAP Desktop user interface extensions;
  • you do not require full portability on all platforms;
  • your code has (or will have) dependencies on a lot of non-standard libraries.

With the standard Python approach extension of SNAP is currently limited to raster data processor (Operator) plugins.

Use the Jython approach, if

  • you plan to develop SNAP Desktop user interface extensions;
  • you require full portability on all platforms;
  • do not require efficient array/raster data processing as provided by numpy and the like (because Jython does yet not well support these);.

Once you made your decision which approach to take, you can head to the dedicated section below.

This approach is explained on the Jython Approach page.

Standard Python (CPython) Approach

With this approach you can use a standard Python (CPython) interpreter installed on your computer (SNAP does not include a CPython interpreter.) The supported versions are Python 2.7, 3.3 to 3.6 64-bit (Linux + Darwin) and both 32-bit and 64-bit (Windows) as well as You need to use a standard Python (CPython) interpreter installed on your computer (SNAP does not include a CPython interpreter.) The supported versions are Python 2.7, 3.3 to 3.10 64-bit (Linux + Darwin) and both 32-bit and 64-bit (Windows) as well as Anaconda distributions. 

Info
titleWindows users

Please note that you must use a 32-bit Python if your SNAP installation is 32-bit and accordingly use a 64-bit Python if your SNAP installation is 64-bit.

The esa_snappy

...

Plugin

Note: The following examples assume that you work with SNAP 10 and the new esa_snappy interface. Differences to the previous snappy interface are outlined in the text or in comment lines in the Python code snippets.

For SNAP 10, esa_snappy is provided as external plugin which allows you to access the SNAP Java API from Python. snappy requires either a SNAP installation or a SNAP build.

Access, Installation and Configuration

For a guideline how to access, install and configure Python for SNAP please see see Configure Python to use the new SNAP-Python (esa_snappy) interface (SNAP version 10+) for the most recent SNAP 10, or Configure Python to use the SNAP-Python (snappy) interface (SNAP versions <= 9) for older SNAP versions.

Examples of SNAP API usage from Python

The following first example reads some raster data and displays/stores raster data as an RGB image:

Code Block
languagepy
titlesnappy 1st Contact
from esa_snappy import ProductIO import numpy# aspackage npto import be imported is now esa_snappy instead of snappy
import numpy as np
import matplotlib.pyplot as plt

p = ProductIO.readProduct('esa_snappy/testdata/MER_FRS_L1B_SUBSET.dim')  # package folder is now esa_snappy instead of snappy
rad13 = p.getBand('radiance_13')
w = rad13.getRasterWidth()
h = rad13.getRasterHeight()
rad13_data = np.zeros(w * h, np.float32)
rad13.readPixels(0, 0, w, h, rad13_data)
p.dispose()
rad13_data.shape = h, w
imgplot = plt.imshow(rad13_data)
imgplot.write_png('radiance_13.png')

...

Due to numerous writers implemented in the SNAP Engine reading and writing data in esa_snappy is relatively simple. The general syntax is: 

Code Block
languagepy
from esa_snappy import ProductIO  p# package = ProductIOto be imported is now esa_snappy instead of snappy

p = ProductIO.readProduct('esa_snappy/testdata/MER_FRS_L1B_SUBSET.dim') # read product  # package folder is now esa_snappy instead of snappy
ProductIO.writeProduct(p, '<your/out/directory>', '<write format>') # write product

...

It makes sense to start exploring the capabilities of esa_snappy by reading a product (see above: snappy 1st Contact) and try out the methods and fields it contains. Calling get on an object in esa_snappy (e.g. p.getRasterHeight()) returns either an Integer or String value or an object. Upon the latter, you can again normally call get again. This might again return an object with fields to return.
In the other direction, you may also set fields. For instance, if you called get and received a String value, you may set the same field of this object with a String.

...

Code Block
languagepy
target_band = target_product.getBand('some_output_band_based_on_rad13')
target_band.setNoDataValue(rad13.getNoDataValue()) # no data value from the source Band object rad13

Often, esa_snappy returns Integers (e.g. p.getSceneRasterWidth()) or Strings (e.g. p.getName()):

...

Code Block
languagepy
p.setAutoGrouping('<a String correctly formatted to be recognized as autogrouping>')


Processing in esa_snappy

Snappy generally offers to ways how to process data:

...

Code Block
languagepy
# 1. Imports

from esa_snappy import ProductIO  # package to be imported is now esa_snappy instead of snappy
from esa_snappy import GPF
from esa_snappy import Hashmap

# 2. Fill parameter Hashmap

parameters = Hashmap()
parameters.put('targetResolution', '10')
parameters.put('referenceBand', 'B4')

# 3. Call Operator

operator_name = 'Resample'
target_product = GPF.createProduct(operator_name, parameters, p)

# 4. Write Product
# Write target Product with ProductIO:

write_format = 'BEAM-DIMAP' # in this case write as BEAM-DIMAP
ProductIO.writeProduct(target_product , <'your/out/directory'>, write_format)

# Alternative solution: Computations are faster when using GPF to write the product instead of ProductIO:
incremental = false # most writer don't support the incremental writing mode (update exsiting file), except BEAM-DIMAP.
GPF.writeProduct(target_product , File(<'your/out/directory'>), write_format, incremental, ProgressMonitor.NULL)

...

Code Block
languagepy
# 1. Imports

import esa_snappy from snappy# importpackage ProductUtilsto be widthimported = p.getSceneRasterWidth() # is now esa_snappy instead of snappy
from esa_snappy import ProductUtils

width = p.getSceneRasterWidth() # often the target product dimensions are the same as the source product dimensions
height = p.getSceneRasterHeight()
target_product = esa_snappy.Product('My target product', 'The type of my target product', width, height)

# 2. Optional: Copy or set meta information

ProductUtils.copyMetadata(p, target_product)

# It is also possible to target specific fields. Just one example:
target_product.setDescription('Product containing very valuable output bands')

# 3. Set product writer
# Set the writer with the write_format defined above (here: 'BEAM-DIMAP'):

target_product.setProductWriter(ProductIO.getProductWriter(write_format))

# 4. Add and configure target Bands
# Now, you could copy bands form the source product if you are interested in writing them to the target product as well. Check out ProductUtils.copyBand() regarding this task.
# Before starting our computations, we must create the computed bands of our target Product:

band_name = 'an_output_band_name'
target_band = target_product.addBand(band_name, snappy.ProductData.TYPE_FLOAT32)

# further configure the created band:
nodata_value = p.getBand(<'source_band_name'>).getNoDataValue()
target_band.setNoDataValue(nodata_value)
target_band.setNoDataValueUsed(True)
target_band.setWavelength(425.0)

# You could set values of other fields, some might be important for creating an output suiting your expectations.

# 5. Write header
# All the structure and meta information we just added to the target_product are still in memory. Hence, we must write its header before writing data. The single argument of writeHeader() is 
# the absolute path to the expected product without file extension. 
# The last String of this path is the target Product name as it is being written.

target_product.writeHeader(<'your/out/directory/product_name'>)

The target Product is ready for data to be written to it. Check out out  esa_snappy examples on GithubGitHub in order to know how to use readPixels() and writePixels() for reading data tiles, rows, columns or single pixels into numpy arrays and write them from output arrays into the respective band of the target Product. These same examples are also provided in the esa_snappy installation directory (see below).

Note

It is worth to note that the order of the parameters width and height is switched from what Python users are familiar with from packages like numpy.
After having created the target product we could now adjust its metadata, e.g. by copying it from the source product using ProductUtils. This is an optional step.

...

More example code of how to use the SNAP API in Python can be found in <snappy<esa_snappy-dir>/examples. There is also a directory <snappy<esa_snappy-dir>/testdata with a single EO test data product (*.dim) in it which you can pass as argument to the various examples. Please try

cd <snappy<esa_snappy-dir>/examples
$ <python-exe> snappy_ndvi.py ../testdata/MER_FRS_L1B_SUBSET.dim

Note that the one and only reference for the SNAP Python API is the SNAP Java API documentation. All Java classes from the API can be "imported" by the the jpy Java-Python bridge  which is implicitely used by esa_snappy. For example:
ProductIOPlugInManager =  snappyesa_snappy.jpy.get_type('org.esa.snap.framework.dataio.ProductIOPlugInManager')
it = ProductIOPlugInManager.getInstance().getAllReaderPlugIns()
Also A few more example snippets can also be found in the snap-examples repository are some more example snippets to find.

Python Operator Plugins

Python can be used to extend SNAP by new raster data processor plugins, i.e. operators. Operators can be executed from the command-line, or invoked from the SNAP Desktop GUI, and be used as nodes in processing XML graphs. In the future, the SNAP development team will open more extension points for Python developers.

Example code for an operator plugin version of the snappy_ndvi.py example from above is provided on GitHub: https://github.com/senbox-org/snap-examples/tree/master/snap-engine-python-operator

How to Configure SNAP to pick up the build output automatically

Find the etc folder in the SNAP installation directory. Inside this directory you will find the snap.conf file. Change the access right of it so that you are allowed to make changes to it. There you will find the extra_clusters property. Specify the path, to the cluster folder of the build output directory.

Code Block
languagexml
extra_clusters="<project_dir>/target/nbm/netbeans/snap"

Ensure to remove the '#' character at the beginning of the line.

Now when you start SNAP the build output is automatically used by SNAP and you can test the latest buildsbe executed from the command-line, or invoked from the SNAP Desktop GUI, and be used as nodes in processing XML graphs.See the guideline how to set up such plugins at What to consider when writing an Operator in Python.