This page provides an introduction to use the SNAP Java API from Python.
Table of Contents |
---|
Introduction
SNAP implementation language is Java and therefore SNAP's "native" API is a Java API. According to the SNAP architecture, the reference SNAP Java API documentation of the two SNAP sub-systems SNAP Engine and SNAP Desktop nearly fully applies to use from Python as well.
...
For either way 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.
...
- 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 data processor (Operator
) plugins.
...
Once you made your decision which approach to take, you can head to the dedicated section below.
Standard Python (CPython) Approach
With this approach you can use an standard Python (CPython) interpreter installed on your computer (SNAP does not include a CPython interpreter.) The supported versions are Python 2.7, 3.3 , and to 3.4 6 64-bit (Linux + Darwin) and both 32-bit and 64-bit (Windows) as well as Anaconda distributions. Please note that Python 3.5 is not yet supported.
Info | ||
---|---|---|
| ||
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 snappy
Module
SNAP provides the Python module snappy
which allows you to access the SNAP Java API from Python. snappy
requires either a SNAP installation or a SNAP build.
Configuration
For how to configure Python for SNAP please see Configure Python to use the SNAP-Python (snappy) interface
...
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 | ||||
---|---|---|---|---|
| ||||
from snappy import ProductIO import numpy as np import matplotlib.pyplot as plt p = ProductIO.readProduct('snappy/testdata/MER_FRS_L1B_SUBSET.dim') 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') |
More example code of how to use the SNAP API in Python can be found in <snappy-dir>/examples
. There is also a directory <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
...
Data IO
Due to numerous writers implemented in the SNAP Engine reading and writing data in snappy is relatively simple. The general syntax is:
Code Block | ||
---|---|---|
| ||
from snappy import ProductIO p = ProductIO.readProduct('snappy/testdata/MER_FRS_L1B_SUBSET.dim |
...
jpy
Java-Python bridge which is implicitely used by snappy
. For example:ProductIOPlugInManager = snappy.jpy.get_type('org.esa.snap.framework.dataio.ProductIOPlugInManager')
it = ProductIOPlugInManager.getInstance().getAllReaderPlugIns()
Python Operator Plugins
Python can be used 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
Jython Approach
Configuration
The Jython approach doesn't need any explicit configuration as Jython is bundled with SNAP.
Using the SNAP Java API from Jython
It is currently not possible to use the SNAP Java API from Jython scripts. This issue will be addressed.
Extending SNAP
Jython plugin
SNAP can be extended using SNAP Jython plugins. A Jython plugin is basically a directory or ZIP file which contains one or more Python modules which define a plugin activator class. Plugin activators are enumerated in a plugin registration file:
<plugin-dir>
one_plugin.py
another_plugin.py
META-INF
services
org.esa.snap.jython.PluginActivator
When any SNAP application starts, for example SNAP Desktop (bin/snap
) or the graph processing tool (bin/gpt
), it loads all plugins found in a number of configurable locations. The first location is the .snap/snap-jython
directory of the users home directory. Other locations can be configured using the configuration parameter snap.jythonExtraPaths
whose value is a colon- (Unix/Darwin) or semicolon- (Windows) separated list of directories to search for plugins.
Plugin activator class
A SNAP extension is a Python class (with any name) defined in a Jython module that defines a no-args start
and stop
method:
Code Block | ||||
---|---|---|---|---|
| ||||
class PluginActivator:
def start(self):
"""
The start method is called once while the SNAP Desktop application is being started.
You would put code here for registrating UI-elements such as actions and windows here.
"""
pass
def stop(self):
"""
The stop method is called once while the SNAP Desktop application is shutting down.
You would put any clean-up code here.
"""
pass |
Example code for a SNAP Desktop extension using Jython can be found on GitHub: https://github.com/senbox-org/snap-examples/tree/master/snap-jython-examples.
Plugin registration file
The plugin registration must be named org.esa.snap.jython.PluginActivator
and must be located in the META-INF/services
subdirectory of your Jython plugin directory or ZIP file. It lists all your plugin activator classes using their fully qualified package path. For the example plugin directory above the content of the file would be:
...
') # read product
ProductIO.writeProduct(p, '<your/out/directory>', '<write format>') # write product
|
Get and Set
It makes sense to start exploring the capabilities of 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 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 | ||
---|---|---|
| ||
rad13 = p.getBand('radiance_13')
rad13.setBandName('just_a_test') |
Sometimes you might only be interested in “copying” a field from one object to another, let’s say from a source band to a target band. Then you may simply call get and set in one line without interacting with the return of get:
Code Block | ||
---|---|---|
| ||
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, snappy returns Integers (e.g. p.getSceneRasterWidth()) or Strings (e.g. p.getName()):
Code Block | ||
---|---|---|
| ||
name = p.getName()
width = p.getSceneRasterWidth()
band_names = p.getBandNames() |
However, you might sometimes walk into Java objects that appear foreign to a Python user. These can e.g. be objects such as the return of:
Code Block | ||
---|---|---|
| ||
p.getBandNames()
>>>[Ljava.lang.String;(objectRef=0x00000000391DEAE8) |
In this case, you simply convert this to a Python list by calling:
Code Block | ||
---|---|---|
| ||
bands = list(p.getBandNames())
bands
>>> ['Oa01_reflectance', 'Oa02_reflectance', 'Oa03_reflectance', 'Oa04_reflectance']
type(bands)
>>> <class 'list'> |
In other cases, using str()converts a Java String to a Python String. For instance, you might be interested in the ‘autogrouping’ of the product. It defines how the bands are grouped in the product. The return of:
Code Block | ||
---|---|---|
| ||
p.getAutoGrouping()
>>> org.esa.snap.core.datamodel.Product$AutoGrouping(objectRef=0x00000000391DEAC8) |
This can be easily converted to a Python String:
Code Block | ||
---|---|---|
| ||
autogrouping = str(p.getAutoGrouping())
autogrouping
>>> 'Oa*_reflectance:Oa*_reflectance_err:A865:ADG:CHL:IWV:KD490:PAR:T865:TSM:atmospheric_temperature_profile:lambda0:FWHM:solar_flux'
type(autogrouping)
>>> <class 'str'> |
You may set the value by:
Code Block | ||
---|---|---|
| ||
p.setAutoGrouping('<a String correctly formatted to be recognized as autogrouping>') |
Processing in snappy
Snappy generally offers to ways how to process data:
Option A is suited when you aim at using only SNAP Engine Operators.
Option B is suited when you aim at doing custom computations for which you need to read data into Python numpy arrays.
Both options can, of course, occur in one workflow.
Option A: Process a product using a SNAP Engine Operator and write the target product
SNAP Operators are available in snappy via GPF.createProduct()
. Its first parameter is a String denoting the name of the Operator as denoted in the Engine and available via GPT. If you have added GPT to your environment variables, you may call GPT from cmd in order to check out the available Operators, their description and parameters. In snappy, we provide the parameters through the second parameter of GPF.createProduct()
. This parameter is a Java Hashmap, an object that is equivalent to a Python dictionary. The parameters must be named exactly with the String parameter name provided in GPT.
Code Block | ||
---|---|---|
| ||
# 1. Imports
from snappy import ProductIO
from snappy import GPF
from 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)
|
Instead of the NULL progress monitor, you can use a different monitor if you like to receive progress messages on the command line.
Code Block | ||
---|---|---|
| ||
def createProgressMonitor():
PWPM = jpy.get_type('com.bc.ceres.core.PrintWriterProgressMonitor')
JavaSystem = jpy.get_type('java.lang.System')
monitor = PWPM(JavaSystem.out)
return monitor
pm = createProgressMonitor()
GPF.writeProduct(target_product , File(<'your/out/directory'>), write_format, incremental, pm) |
Option B: Process a product using custom data computations in Python
Using an implemented Operator might not be enough in cases where you aim to implement your own computation. The methods readPixels()
and writePixels()
help you to retrieve the necessary for the computation and save the result. We recommend to completely set up your target product in your script before computation starts. As in the examples above, p is still our source Product.
Code Block | ||
---|---|---|
| ||
# 1. Imports
import snappy
from snappy import ProductUtils
width = p.getSceneRasterWidth() # often the target product dimensions are the same as the source product dimensions
height = p.getSceneRasterHeight()
target_product = 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 snappy examples on Github 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.
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. |
Further code examples
More example code of how to use the SNAP API in Python can be found in <snappy-dir>/examples
. There is also a directory <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-dir>/examples
$ <python-exe> snappy_ndvi.py ../testdata/MER_FRS_L1B_SUBSET.dim
jpy
Java-Python bridge which is implicitely used by snappy
. For example:ProductIOPlugInManager = snappy.jpy.get_type('org.esa.snap.framework.dataio.ProductIOPlugInManager')
it = ProductIOPlugInManager.getInstance().getAllReaderPlugIns()
Python Operator Plugins
Python can be used 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