4
Nov
2016
Author: Brian Griglak
Since the creation of custom ENVITasks in ENVI 5.2 SP1, there has been the requirement that your procedure must commit all output objects to disk. There was the rule that the procedure wrapped by the task must have an input keyword telling it the filename to use to write the object to. In the task template, you would have an output parameter that mapped to that keyword, and then during task loading the framework would magically create an input parameter for you, mapped to the same keyword with TYPE set to “ENVIURI”.
The automatic creation of the input parameter and the internal correlation of the two parameters were done with the best of intentions, to simplify the process of creating custom tasks. Alas the user feedback on this feature wasn’t always as rosy as we hoped.
So in ENVI 5.4 we’re shaking things up and giving you the task developer more control. If you still use the “version” property in the task template, and have it set to “5.2.1”, “5.3”, or “5.3.1”, then you’ll get the old behavior. But if you switch to using “schema” set to the value “envitask_3.0”, then a new set of rules apply to the procedure, and what you can do inside it. In the new paradigm, your procedure will have separate keywords for the input filename and output object reference. If you like, you can skip the filename keyword completely and return an object that hasn’t been tethered to disk at all. This makes life much easier for types like ENVIGCPSet and ENVITiePointSet, but also allows for a procedure that constructs a complex virtual raster chain based on the other input parameters.
You might be asking why you would want a task to return a virtual raster. With the new Classification Framework that is part of ENVI 5.4, you need to make sure that you prepare the data you want to run through a trained classifier the same way you prepared your training data. One way to do this is to create a task that returned the preprocessed data in a consistent way. If you can get away with not having to save that preprocessed attribute raster to disk, why not take advantage of the time and space advantages of using a virtual raster.
The following example is a modified version of the new Custom Task Tutorial in the ENVI 5.4 release. That task wraps a procedure that uses a number of other tasks internally to perform all the preprocessing. Here I’ve modified it to use virtual rasters and other ENVI API function calls to avoid ever writing a file to disk.
The code goes through a number of steps, which I will describe after showing it:
pro SentinelVegIndices_blog, INPUT_RASTER_10M=raster10m, $ INPUT_RASTER_20M=raster20m, $ OUTPUT_RASTER=outputRaster compile_opt idl2, hidden ; Get the spatial reference of the 10-meter raster spatialRef = raster10m.SPATIALREF coordSys = ENVICoordSys(COORD_SYS_CODE=spatialRef.COORD_SYS_CODE) ; Create a spatial grid definition grid = ENVIGridDefinition(coordSys, $ PIXEL_SIZE=spatialRef.PIXEL_SIZE, $ NCOLUMNS = raster10m.NCOLUMNS, $ NROWS = raster10m.NROWS, $ TIE_POINT_MAP=spatialRef.TIE_POINT_MAP, $ TIE_POINT_PIXEL = spatialRef.TIE_POINT_PIXEL) ; Regrid the 20-meter bands to 10 meters regriddedRaster = ENVISpatialGridRaster(raster20m, GRID_DEFINITION=grid) ; Create a layer stack layerStack = ENVIMetaspectralRaster([raster10m, regriddedRaster], $ SPATIALREF=spatialRef) ; Compute image statistics stats = ENVIRasterStatistics(layerStack) ; Perform dark subtraction as an alternative to atmospheric correction bandRasters = ObjArr(layerStack.nBands) for i = 1, layerStack.nBands do begin expression = 'b' + i.ToString() + ' - ' + stats.Min[i-1].ToString() bandRasters[i-1] = ENVIPixelwiseBandMathRaster(layerStack, expression) endfor bandStackRaster = ENVIMetaspectralRaster(bandRasters, $ SPATIALREF=spatialRef) ; we need to put the wavelengths back into the band stack, ; they were removed by the band math metadata = ENVIRasterMetadata() metadata['wavelength'] = layerStack.Metadata['wavelength'] metadata['wavelength units'] = layerStack.Metadata['wavelength units'] correctedRaster = ENVIMetadataOverrideRaster(bandStackRaster, $ METADATA_OVERRIDE=metadata) ; Scale pixel values from 0 to 1 gains = MAKE_ARRAY(layerStack.NBANDS, /FLOAT, VALUE=0.0001) offsets = FLTARR(layerStack.NBANDS) scaledRaster = ENVIGainOffsetRaster(correctedRaster, gains, offsets) ; Create vegetation indices indices = [ 'Enhanced Vegetation Index', $ 'Global Environmental Monitoring Index', $ 'Leaf Area Index', $ 'Plant Senescence Reflectance Index', $ 'Red Edge Normalized Difference Vegetation Index' ] outputRaster = ENVISpectralIndexRaster(scaledRaster, indices) end
The first step is to upsample the 20m raster to 10m pixels, which in the tutorial is performed using the ENVIRegridRasterTask. This can be done with the ENVISpatialGridRaster virtual raster, once we have constructed an ENVIGridDefinition with the appropriate mixing of properties form the 10m and 20m rasters.
Next the tutorial uses the ENVIBuildBandStackTask to build a metaspectral stack of all the 10m bands. Here we use the ENVIMetaspectralRaster virtual raster, though we have to pass in the original 10m raster’s spatial reference to keep this raster registered on the map.
Next is the dark subtraction. The tutorial uses the ENVIRasterStatisticsTask, but here we just use the API function ENVIRasterStatistics() to accomplish the same thing.
The band minima values are used to perform dark object subtraction. The tutorial uses the ENVIDarkSubtractionCorrectionTask, which handles this as a single raster. Here I had to build separate band math equations for each band, as the ENVIPixelwiseBandMathRaster virtual raster always returns a single band output, so I have to build an array of band math expressions and then metaspectrally stack the results, again passing in the spatial reference.
One pitfall of the band math is that it removes most of the spectral metadata, so I have to put the wavelength metadata back into the raster so the spectral index calculations select the correct bands. I do this with ENVIMetadataOverrideRaster(), using a copy of the original metaspectral raster’s metadata values.
The raster is then scaled down by a factor of 10000.0 with ENVIGainOffsetRaster, to simulate the atmospheric correction better and produce spectral index values that are more accurate. Lastly, the scaled raster is passed into ENVISpectralIndexRaster to calculate 5 different spectral indices.
Once this has been written, we can use an almost identical .task file to wrap the procedure. The main difference is that we no longer need to specify the OUTPUT_URI parameter, and I had a slightly different procedure name. This task’s output raster is never commited to disk, but it can be used as input to another task by calling ENVIRaster::Dehydrate() on it, which yields a 25KB JSON representation.
Here is the updated .task file:
{ "name": "SentinelVegIndices_blog", "base_class": "ENVITaskFromProcedure", "routine": "SentinelVegIndices_blog", "display_name": "Compute Sentinel-2A Vegetation Indices", "description": "This task regrids the visible and near-infrared bands of a Sentinel-2A Level-1C 20-meter image to 10 meters. It creates a layer stack of all bands. It applies dark-object subtraction as a simple alternative to atmospheric correction. It scales the reflectance pixel values from 0 to 1, then computes a select group of vegetation indices.", "schema": "envitask_3.0", "parameters": [ { "name": "INPUT_RASTER_10M", "display_name": "Select a 10-meter image", "type": "ENVIRASTER", "direction": "input", "required": true, "description": "Select a Sentinel Level-1C 10-meter image." }, { "name": "INPUT_RASTER_20M", "display_name": "Select a 20-meter image", "type": "ENVIRASTER", "direction": "input", "required": true, "description": "Select a Sentinel Level-1C 20-meter image." }, { "name": "OUTPUT_RASTER", "display_name": "Output image", "type": "ENVIRASTER", "direction": "output", "required": true, "description": "This is a reference to the output raster." } ] }
Categories: IDL Blog | IDL Data Point
Tags:
28
Oct
Author: Daryl Atencio
A recent project I worked on required custom polygons – Controlled by my application – to be added to the ENVI display. The following code defines an object class that allows the user to place a polygon in the ENVI display using window coordinates. To run the application:
;------------------------------------------------------------------------------
;+
; Lifecycle method for destroying the object
;-
pro my_polygon::Cleanup
compile_opt idl2, logical_predicate
self->Destruct
end
; Cleans up the member variables of the object class
pro my_polygon::Destruct
self->IDLmiManipGraphicOverlay::Cleanup
self->IDLmiManipLayer::Cleanup
self->IDLgrPolygon::Cleanup
; Lifecycle method for initializing the class
;
; :Returns:
; 1 if the object initializes successfully and 0 otherwise
; :Params:
; xy: in, optional, type="int"
; a [2,n] array containing the [x,y] points in window coordinates
; :Keywords:
; POLYGONS: in, optional, type="int"
; An integer array of one or more polygon descriptions. See IDL help for
; IDLgrPolygon for more information
; _REF_EXTRA: Used to set properties of the inherited IDLgrPolygon
function my_polygon::Init, xy, $
POLYGONS=poly, $
_REF_EXTRA=refExtra
void = self->IDLmiManipGraphicOverlay::Init(_EXTRA=refExtra)
if ~void then begin
return, 0
endif
self->InitializeDataspace
self->SetupManipulatorGraphics
self->InitializeGraphics
if n_elements(xy) then begin
self->SetProperty, DATA=xy, POLYGONS=poly
if n_elements(refExtra) then begin
self->SetProperty, _EXTRA=refExtra
return, 1
; This method initializes the data space used by the graphics layer
pro my_polygon::InitializeDataspace
e = envi(/CURRENT)
eView = e->GetView()
eView->GetProperty, _COMPONENT=ecfViewGroup
oDS = ecfViewGroup->GetDescendants(BY_TYPE='DATASPACE', /FIRST_ONLY)
self._oTargetDS = oDS
; Initializes the graphics components of the class
pro my_polygon::InitializeGraphics
void = self->IDLgrPolygon::Init(COLOR=[255,0,0], /PRIVATE, THICK=2)
self._oGrOverlay->IDLmiContainer::Add, self
; This method is for setting class properties
pro my_polygon::SetProperty, $
DATA=data, $
if n_elements(data) then begin
self->SetData, data, POLYGONS=poly
self->IDLgrPolygon::SetProperty, _EXTRA=refExtra
self->IDLmiManipLayer::SetProperty, _EXTRA=refExtra
; This method maps the points from window coordinates to map coordinates and
; adds the mapped points to the IDLgrPolygon.
; xy: in, required, type="int"
; A [2,n] array of points (In window coordinates) to be added to the polygon
pro my_polygon::SetData, xy, $
POLYGONS=poly
self._oTargetDS->WindowToVis, reform(xy[0,*]), reform(xy[1,*]), xVis, yVis
self->IDLgrPolygon::SetProperty, DATA=transpose([[xVis],[yVis]]), $
; Class structure definition
pro my_polygon__define
void = {my_polygon $
, inherits IDLmiManipGraphicOverlay $
, inherits IDLmiManipLayer $
, inherits IDLgrPolygon $
}
pro envi_polygon_example
if ~isa(e, 'envi') then begin
e = envi()
file = FILEPATH('qb_boulder_msi', ROOT_DIR=e.ROOT_DIR, SUBDIRECTORY=['data'])
eRaster = e->OpenRaster(file)
eLayer = eView->CreateLayer(eRaster)
xy = [[470,140],[560,140],[560,230],[470,230], $
[750,115],[1200,115],[1200,665],[750,665]]
conn = [4,0,1,2,3,4,4,5,6,7]
oPolygon = obj_new('my_polygon', xy, LINESTYLE=5, POLYGONS=conn, STYLE=1)
Tags: IDL, ENVI, custom polygons
27
Author: Daniel Platt
One of the greatest features of the Geospatial Services Framework (GSF), in my opinion, is the fact that it is built upon Node.js. There are many reasons why this is great, but I wanted to talk about one in particular which is the amount of customization this provides.
Below I will show a simple but powerful example of this customization, but before I get there, I want to give a quick overview of both GSF and Node.js.
We have improved ENVI Service Engine by adding GSF – a lightweight, but powerful framework based on Node.js that can provide scalable geospatial intelligence for any size organization. I like to describe it as a “messenger” system that provides a way to communicate between the web and our various products or “engines” that provide analytics such as ENVI, IDL and more.
//Import express, used by node.js to setup rest endpoints var express = require('express'); //Import AWS, used to interact with Amazon Web Services including S3 storage var aws = require('aws-sdk'); //Extra tools that should be included in request handlers var extend = require('util')._extend; var defaultConfig = require('./config.json'); /** * Dynamic Request Handler */ function DynUiHandler() { var s3Bucket, s3; //Setup config options var config = {}; extend(config, defaultConfig); //Grab information for S3 from config.json s3Workspace = config.S3Root; s3Bucket = config.S3Bucket; // Setup S3 s3 = new aws.S3(config); /** * List files in the s3 bucket. * @param {object} req - The Express request object. * @param {object} res - The Express response object. */ function listS3Data(req, res) { //Take the parameter passed from the REST call and set that as the bucket to be accessed in the call to s3. var params = { Bucket: req.params.bucket }; //Call the S3 package's built in listObjects function to return what is avaiable in the specified bucket s3.listObjects(params, function(err, data) { //check if error occured and if so, halt and report it to client. if (err) { var code = err.code || 500; var message = err.message || err; res.status(code).send({ error: message }); } // If no error, push every object found in the response with a '.dat' extension to an array that will be returned to client else { var files = []; //Look at each file contained in data returned by s3.listObjects() data.Contents.forEach(function(file) { //Searches for files with .dat in the bucket requested if(file.Key.endsWith('.dat')){ //If found, store that file information in the files array. files.push(file); } }); } //send the files array containing metadata of all .dat files found. res.send(files); }); }; //Initialize request handler, run when GSF starts up. this.init = function(app) { // Set up request handler to host the html subdirectory as a webpage. app.use('/dynui/', require('express').static(__dirname + '/html/')); // Set up a rest call that runs listS3Data and supplies the accomponying bucket parameter. app.get('/s3data/:bucket', listS3Data); }; } // module.exports = DynUiHandler;
Categories: ENVI Blog | Imagery Speaks
Tags: Cloud, ENVI Services Engine, customization, GSF
25
Author: Joey Griebel
If all goes according to plan, next month the GOES-R weather satellite will be launching and the next generation of weather forecasting, solar activity monitoring, and lightening detection will be here. This advanced satellite will change how quickly and accurately we are able to monitor and predict hazardous weather and help give those in harm’s way the time needed to prepare and evacuate. The GOES-R satellite will include:
Advanced Baseline Imager (ABI) – an advanced imager that has 3 times more channels, 4 times better resolution , and 5 times faster than before. All of this leads to better observation of severe storms, fire, smoke, aerosols, and volcanic ash.
Geostationary Lightening Mapper - The lightning mapper will allow mapping of lightning strikes on ground, as well as lightning in the atmosphere. Researchers have found that an increase in lightning activity may be a sign of tornadoes forming, thus providing the data to detect tornadoes faster.
Space Weather Observation - GOES-R will work with NOAA instruments to gather information on radiation hazards from sun that can interfere with communication and navigation systems, damage satellites, threaten power utilities.
Harris Corporation has been supporting NOAA with some aspects on the GOES-R Satellite construction and will be providing the Ground System Support, as well as the 16.4 Meter triband antenna needed to stay in touch with it.
Now what does this have to do with ENVI? Once GOES-R is operational and collecting data, ENVI will be working to support the Harris Weather groups WXconnect systems for data validation and visualization, supporting the ABI data directly in ENVI, as well as working with NOAA to help continue to create advanced products in the future. It is an exciting time for NOAA with this milestone launch and an exciting time to be working with ENVI to get to work the advanced data that will be coming down!
Below are the baseline products, as well as some future products that can be expected.
BASELINE PRODUCTS
FUTURE PRODUCTS
Advanced Baseline Imager (ABI)
Absorbed Shortwave Radiation: Surface
Aerosol Detection (Including Smoke and Dust)
Aerosol Particle Size
Aerosol Optical Depth (AOD)
Aircraft Icing Threat
Clear Sky Masks
Cloud Ice Water Path
Cloud and Moisture Imagery
Cloud Layers/Heights
Cloud Optical Depth
Cloud Liquid Water
Cloud Particle Size Distribution
Cloud Type
Cloud Top Height
Convective Initiation
Cloud Top Phase
Currents
Cloud Top Pressure
Currents: Offshore
Cloud Top Temperature
Downward Longwave Radiation: Surface
Derived Motion Winds
Enhanced "V" / Overshooting Top Detection
Derived Stability Indices
Flood/Standing Water
Downward Shortwave Radiation: Surface
Ice Cover
Fire/Hot Spot Characterization
Low Cloud and Fog
Hurricane Intensity Estimation
Ozone Total
Land Surface Temperature (Skin)
Probability of Rainfall
Legacy Vertical Moisture Profile
Rainfall Potential
Legacy Vertical Temperature Profile
Sea and Lake Ice: Age
Radiances
Sea and Lake Ice: Concentration
Rainfall Rate / QPE
Sea and Lake Ice: Motion
Reflected Shortwave Radiation: TOA
Snow Depth (Over Plains)
Sea Surface Temperature (Skin)
SO2 Detection
Snow Cover
Surface Albedo
Total Precipitable Water
Surface Emissivity
Volcanic Ash: Detection and Height
Tropopause Folding Turbulence Prediction
Geostationary Lightning Mapper (GLM)
Upward Longwave Radiation: Surface
Lightning Detection: Events, Groups & Flashes
Upward Longwave Radiation: TOA
Space Environment In-Situ Suite (SEISS)
Vegetation Fraction: Green
Energetic Heavy Ions
Vegetation Index
Magnetospheric Electrons & Protons: Low Energy
Visibility
Magnetospheric Electrons & Protons: Med & High Energy
Solar & Galactic Protons
Magnetometer (MAG)
Geomagnetic Field
Extreme Ultraviolet and X-ray Irradiance Suite (EXIS)
Solar Flux: EUV
Solar Flux: X-ray Irradiance
Solar Ultraviolet Imager (SUVI)
Solar EUV Imagery
20
Author: Austin Coates
Recently, I was given the chance to practice some spectroscopy and in preparation for the project, I realized that I did not have a simple way to visualize the variations in different absorption features between very discreet wavelengths. The method that I elected to employ for this task is called continuum removal (Kokaly, Despain, Clark, & Livo, 2007). This method allows you to compare different spectra and essentially normalize the data so that they can be more easily compared with one another.
To use the algorithm, you first select the region that you are interested in (for me this was between 550 nm and 700 nm -this is the region of my spectra that deals with chlorophyll absorption and pigment). Once the region is selected then a linear model is fit between the two points and this is called the continuum. The continuum is the hypothetical background absorption feature, or artifact or other absorption feature, which acts as the baseline for the target features to be compared against (Clark, 1999). Once the continuum has been set then the continuum removal process can be performed on all spectra in question using the following equation (Kokaly, Despain, Clark, & Livo, 2007).
RC is the resulting continuum removed spectra, RL is the continuum line and, Ro is the original reflectance value.
Figure 1: Original spectra of two healthy plants. The dotted line denotes the continuum line. The x axis shows wavelengths in nm and the y axis represents reflectance.
Figure 2: The continuum removal for wavelengths 550 nm - 700 nm.
The resulting code gives you a tool that will take in two spectral libraries, with one spectra per library, and return two plots similar to what is shown in Figure 1 and Figure 2.
pro Continuum_Removal
compile_opt IDL2
Spectra_File_1 =
Spectra_File_2 =
; Find the bounds for the feature
FB_left = 550
FB_right =700
; Open Spectra 1
oSLI1 = ENVISpectralLibrary(Spectra_File_1)
spectra_name = oSLI1.SPECTRA_NAMES
Spectra_Info_1 = oSLI1.GetSpectrum(spectra_name)
; Open Spectra 2
oSLI2 = ENVISpectralLibrary(Spectra_File_2)
spectra_name = oSLI2.SPECTRA_NAMES
Spectra_Info_2 = oSLI2.GetSpectrum(spectra_name)
; Get the wavelengths
wl = Spectra_Info_1.wavelengths
; Create Bad Bands List (this removes some regions of the spectra associated with water vapor absorption)
bb_range = [[926,970],[1350,1432],[1796,1972],[2349,2500]]
bbl = fltarr(n_elements(wl))+1
dims = size(bb_range, /DIMENSIONS)
for i = 0 , dims[1]-1 do begin
range = bb_range[*,i]
p1 = where(wl eq range[0])
p2 = where(wl eq range[1])
bbl[p1:p2] = !VALUES.F_Nan
endfor
;Plot oSLI1 / oSLI2
p = plot(wl, Spectra_Info_1.spectrum*bbl, xrange = [min(wl, /nan),max(wl, /nan)],$
yrange=[0,max([Spectra_Info_1.spectrum*bbl,Spectra_Info_2.spectrum*bbl], /nan)], thick = 2, color = 'blue')
p = plot(wl, Spectra_Info_2.spectrum*bbl, /overplot, thick = 2, color = 'green')
; create the linear segment
Spectra_1_y1 = Spectra_Info_1.spectrum[where( wl eq FB_left)]
Spectra_1_y2 = Spectra_Info_1.spectrum[where( wl eq FB_right)]
pl_1 = POLYLINE([FB_left,FB_right], [Spectra_1_y1, Spectra_1_y2], /overplot, /data, thick = 2, LINESTYLE = '--')
Spectra_2_y1 = Spectra_Info_2.spectrum[where( wl eq FB_left)]
Spectra_2_y2 = Spectra_Info_2.spectrum[where( wl eq FB_right)]
pl_2 = POLYLINE([FB_left,FB_right], [Spectra_2_y1, Spectra_2_y2], /overplot, /data, thick = 2, LINESTYLE = '--')
; Get the equation of the line
LF_1 = LINFIT([FB_left,FB_right], [Spectra_1_y1, Spectra_1_y2])
LF_2 = LINFIT([FB_left,FB_right], [Spectra_2_y1, Spectra_2_y2])
; Get the values between the lower and upper bounds
x_vals = wl [ where(wl eq FB_left) : where(wl eq FB_right)]
; Compute the continuum line
RL_1 = LF_1[0] + LF_1[1]* x_vals
RL_2 = LF_2[0] + LF_2[1]* x_vals
; Perform Continuum Removal
Ro_1 = Spectra_Info_1.spectrum[ where(wl eq FB_left) : where(wl eq FB_right)]
RC_1 = Ro_1 / RL_1
Ro_2 = Spectra_Info_2.spectrum[ where(wl eq FB_left) : where(wl eq FB_right)]
RC_2 = Ro_2 / RL_2
; Plot the new Continuum Removal Spectra
pl_RC_1 = plot(x_vals, RC_1, color = 'Blue', xrange = [min(x_vals, /NAN), max(x_vals, /NAN)] )
pl_RC_2 = plot(x_vals, RC_2, color = 'Green', /overplot)
Kokaly, R. F., Despain, D. G., Clark, R. N., & Livo, K. E. (2007). Spectral analysis of absorption features for mapping vegetation cover and microbial communities in Yellowstone National Park using AVIRIS data.
Clark, R. N. (1999). Spectroscopy of rocks and minerals, and principles of spectroscopy. Manual of remote sensing, 3, 3-58.
New in ENVI 5.4: ENVITask Returning a Virtual Raster
Sign Up for News & Updates: Stay informed with the latest news, events, technologies and special offers.