Overview
This is a sample Python script that utilizes Opticks and the Python Scripting Extension for Opticks. This script allows you to load a hyperspectral or multispectral image in Opticks and then performs an NDVI (Normalized Difference Vegetation Index) and generates the results as a new image in Opticks. After that you can view the NDVI results using the normal histogramming and visualization provided by Opticks. Or you can use the export capability of Opticks to save the NDVI results to a file.
If you want to execute this script in Opticks while learning about it, see the Quick Start.
What Will I Learn?You will learn the following about the Python scripting extension for Opticks:
|
What Do I Need?
|
The Script
Let's discuss this script as two parts:
- Find the index of the bands that contains the red and near ir wavelength of the currently selected image in Opticks.
- Execute the "Band Math" plug-in of Opticks with the NDVI expression, "(NirBand - RedBand)/(NirBand + RedBand)". The Band Math plug-in will do all the heavy lifting of manipulating all the pixels of the given bands to generate a single band result.
Before you continue, let me offer the following reading tips:
- You can type every single line of Python code you see below directly into the "Python" tab of the Scripting Window. Just make sure you have loaded the image first.
- You should also take a look at the Quick Start if you want to be able to run this script before or while you are reading the below explanation.
Part 1. Locate band index of red and nir bands in image.
The first thing we need to do is get a handle to the active view, this is accomplished with the following Python code.
Now that we have a handle to the active view, we need to get a handle to the image being displayed by that view. In Opticks, a given view can actually display any number of layers. One of the types of layers is a RasterLayer, which displays a multi-band image. But each view in Opticks is required to have at least one image and this is known as the primary. So, for the purposes of this script we will just grab a handle to the primary image. So, the code for this:
Now that we have a handle to an image, we can determine the wavelengths for each of the bands in the image. This information is stored in the "metadata". So, first we need to construct a Python object to query the metadata.
Opticks represents metadata as key/value pairs and allows nesting of metadata objects, so we have conveniently wrapped this up into a object (opticks.metadata.DynamicObject) that behaves much like a Python dictionary. So, as aside the following types of operations will work:
For Opticks, we store the wavelength information for the bands in the metadata utilizing the nesting ability provided by the metadata. We provide the capability to store the center wavelength values for each band in the metadata. This information is stored in the metadata under "Special/Band/CenterWavelengths". So, you would access it using the following Python code.
We also provide the capability to store start wavelengths for each band, under "Special/Band/StartWavelengths". And the capability to store end wavelengths for each band as well, under "Special/Band/EndWavelengths". These work identical to center wavelengths demonstrated earlier and are also stored in microns.
A few caveats about band wavelength information in Opticks.
- There is NO requirement for an image to have wavelength information stored in its metadata.
- If stored, there is NO requirement on which part of the wavelength information (start, center, end) is provided.
- There is NO ordering requirement relative to bands and their wavelengths. Meaning an image could have a list of center wavelengths as follows: 1.0, 0.620, 0.900, 0.700, 0.500 This is completely valid.
Ok, so now that we have the wavelength information for all bands of the currently loaded image, we need to find the index or band number associated with the red wavelength and near ir wavelength. Given the above caveats about storing band wavelength information, we need a little Python magic to locate an index. And in the spirit of giving I've made this particular lookup function forgiving it that it will search for all bands in a wavelength range and then return the band in the middle of the matched bands. So, I introduce:
Ok, so part one of the script is complete.
Part 2. Execute NDVI expression using Band Math plug-in.
So, we need to actually perform the NDVI and generate a new image and window in Opticks. This might seem complicated, but I'm going to do it in 20 lines or less. No, you want better? How about 6 lines? Will 9 lines work if I promise only to add comments?
Ok, now that the show is over let's break this down. The "Band Math" plug-in expects a mathematical formula using variables starting with "b" to represent all the pixels of a given band. So "b0" would reference the first band of the given image. I'll get to what "given image" means in a second. And "b25" would reference the twenty sixth band of the image.
So, assuming for an hypothetical image that b11 was the red band and b87 was the near ir band, the following expression would tell "Band Math" how to generate NDVI results: "(b87 - b11)/(b87 + b11)". So, with that in mind line number 1 above should make a lot more sense now.
So, now that we have the expression that "Band Math" needs, let's get to executing it. Before we can execute it, we have to create an instance of the plug-in. We do that with the following Python code:
The first argument is the name of the plug-in and the second argument configures the execution mode of the plug-in. A plug-in can execute in batch mode as I am showing, which means it should not create ask the user any questions and minimize the creation of graphical elements. Or it can execute interactively, (e.g. batch=False) which is the opposite behavior.
You can use opticks.execute.PlugIn to create an instance of any Opticks plug-in, including plug-ins provided by installed extensions. For a list of all available plug-ins, go to the "Plug-Ins" tab of the Session Explorer Window in Opticks and expand the tree. However, not all plug-ins are executable, meaning not all will work properly or very well using opticks.execute.PlugIn. To be safe, you should stick to those plug-ins listed in the "Wizard Builder". You can open the Wizard Builder from the "Tools" menu on the main menu.
Ok, so now that we an instance of the "Band Math" plug-in, we need to execute it. So, a quick intro to how executable plug-ins work in Opticks.
- Set any plug-in input arguments
- Execute the plug-in and check the result of execution (e.g. True for success, False for failure)
- Query the value of any plug-in output arguments
Having said that, another quick introduction to plug-in arguments. There are two argument lists, the input argument list and output argument list. Each argument list can have any number of individual arguments. Each argument has a name and a type. Each argument will also have a default value. The other thing to be mindful of is that technically speaking each executable plug-in can have 4 argument lists:
- An input and output argument when creating an instance of the plug-in with a value of batch=True, otherwise known as the batch input argument list and batch output argument list.
- An input and output argument when creating an instance of the plug-in with a value of batch=False, otherwise known as the interactive input argument list and interactive output argument list.
Ok, now that we have quick primers to executable plug-ins out of the way, let's look at the Python code again:
If we look at line 1, this sets the value of the "Data Element" input argument to the image (e.g. opticks.data.raster.RasterElement ) we acquired back in Part 1 on this script. (As an aside, technically the "Data Element" argument requires a C++ type of RasterElement* from the C++ Opticks API. The cool part here is that our Python interface is smart enough to make all that happen behind the scenes).
And if we look at line 2, this sets the value of the "Input Expression" input argument to the Python string we created earlier in the script.
So, now all we need to do is execute the plug-in so that "Band Math" will do all the heavy lifting. We simply do that by treating the opticks.execute.PlugIn instance as a function and calling it.
Quick Start
- Download and install everything listed under "What Do I Need?"
- Download the NDVI script bundled into an Opticks extension and install it. See How To Install an Extension for details on installing.
- Start up Opticks and display the "Python Samples" toolbar if it isn't already visible.
- Load an image to calculate the NDVI for. Look under "What Do I Need?" for an ideal image to use. You can load the image by dragging and dropping the file onto the Opticks application window. Or you can load the image using the "File->Import..." menu item.
- Once the image is loaded, select the "Python Samples->NDVI For Current Image" from the "Python Samples" toolbar to execute the Python script. This will calculate the NDVI for the image window you have currently selected and will create a new window with the NDVI results.
- You can find the Python script in "OPTICKS_INSTALL\SupportFiles\site-packages\ndvi.py", assuming you installed the script as detailed in step 2.