9826 Rate this article:

An ENVI 5 extension


Last week, I showed an example of an ENVI 5 batch program. This week, I’ll show an extension, the successor to a user function in ENVI Classic. (Be warned, though, that this turned into a long post.)

Extensions are programs executed in an interactive ENVI session. Extensions allow programmatic control over not only the file access and analysis components of ENVI, but also the UI. Like batch programs, extensions may use routines from both the ENVI and IDL libraries. ENVI+IDL is required to build an extension, but by compiling an extension into an IDL SAVE file, the extension can be executed in an ENVI (runtime version) session. I’ll outline this technique at the end of the post. In this example, a user is prompted to select a file containing a raster image. The Prewitt edge enhancement operator is applied to each band of the image, with the result written to a new raster file. The first band of the input image is displayed in a new layer in the ENVI 5 UI, with the first band of the output image displayed in the same view as a portal. Here’s the start of the extension code:

pro envi5_extension_ex
   compile_opt idl2

   catch, err
   if err ne 0 then begin
      catch, /cancel
      if e ne !null then $
         e.reporterror, 'Error: ' + !error_state.msg $
      else $
         message, !error_state.msg, /continue, /noname
      message, /reset

   e = envi(/current)
   if e eq !null then $
      message, 'This extension requires an interactive ENVI session. Exiting.' 

I’ve chosen to write this extension as a procedure (a function or class would also be fine; note there’s an ENVI extension wizard in the IDL Workbench, located under the File menu, that sets up skeleton code for an extension). The CATCH block handles errors thrown in the program. Call the ENVI function to get the reference to the current ENVI session. If ENVI isn’t running, MESSAGE throws an error that's handled by the CATCH block. Next, prompt the user for a raster file to load:

   in_raster = e.ui.selectinputdata(/raster)
   if in_raster eq !null then return 

The SelectInputData method of the ENVIUI object presents a file picker dialog, the ENVI 5 analog to ENVI_SELECT in Classic. It returns an ENVIRaster object. Recall that an ENVIRaster is analogous to a FID in Classic, but it also holds metadata. If the user cancels the operation, then exit the extension. Now prepare for the processed results:

   outdir = e.getpreference('output_directory')
   outfile = outdir + 'envi5_extension_ex.img'
   if file_test(outfile) then begin
      basenames = file_basename(outfile, '.img') + ['.img', '.img.enp', '.hdr']
      file_delete, outdir + basenames, /allow_nonexistent
   out_raster = e.createraster(outfile, inherits_from=in_raster)

In a manner similar to the batch program example from last week, I’ve set up a path to a file in the user’s output directory. The CreateRaster method makes a new ENVIRaster for the output. Because CreateRaster won’t allow an existing file to be overwritten, I've included some IDL code to delete the output file and its support files, if they exist. By using the INHERITS_FROM keyword, the CreateRaster method uses metadata from the input raster to populate the metadata for the output raster. Now perform the processing. Iterate over the input bands, applying the Prewitt edge enhancement operator (with the IDL PREWITT function) to each and writing the result to the output file:

   widget_control, /hourglass
   for i=0, in_raster.nbands-1 do begin
      band = in_raster.getdata(bands=i)
      edge = bytscl(prewitt(band))
      out_raster.setdata, edge, bands=i

The GetData method is the ENVI 5 analog to ENVI_GET_DATA in Classic. The Save method is the analog to ENVI_WRITE_ENVI_FILE. Finally, visualize the result in the ENVI UI. Using the default view, create new layers for the input and output images and display the first band of each, with the output displayed in a portal:

   view = e.getview()
   layer1 = view.createlayer(out_raster, bands=0, /clear_display)
   layer2 = view.createlayer(in_raster, bands=0)
   portal = view.createportal(layer=layer1, size=[150,150])

As it currently stands, this extension could be executed in an interactive ENVI session with the Run button in the IDL Workbench, or by calling it from the IDL command prompt:

ENVI> envi5_extension_ex
% Compiled module: ENVI5_EXTENSION_EX.

However, analogous to a user function in Classic, an extension can be set up to automatically load into ENVI on startup and be visible in the Toolbox. To do this, two additional steps are needed. First, create an “extensions_init” program:

pro envi5_extension_ex_extensions_init
   compile_opt idl2

   e = envi(/current)
   e.addextension, 'Prewitt Edge Enhancement', 'envi5_extension_ex'

This program is called by ENVI at startup. The purpose of this program is to add the item “Prewitt Edge Enhancement” to the Extensions folder of the ENVI 5 Toolbox, and to associate the program ENVI5_EXTENSION_EX with this item. Note that the name of this program is the name of the extension followed by _extensions_init. This program can be placed in the same file as the extension code, or in a separate file. This is the analog to the “define_buttons” routine for user functions in ENVI Classic. Second, the extension, along with the “extensions_init” program, must be placed in one of three locations: either a directory defined by the ENVI_EXTENSIONS environment variable, the extensions directory included in the ENVI installation, or the default extensions directory in the user's home directory. The ENVI Help provides more detail: see Contents > Programming > Toolbox Extensions. One final, optional, step. The extension in this example is written in IDL source code, and therefore can only be used in ENVI+IDL, not ENVI, which lacks the IDL compiler. You can deploy an extension in ENVI if you build the extension and its dependencies into an IDL SAVE file. Here are the steps used for the example above: Reset the current IDL session:

IDL> .reset

This removes all variables and compiled routines from the IDL session. Next, compile the extension with the Compile button in the IDL Workbench or the .compile executive command:

IDL> .compile envi5_extension_ex

Call RESOLVE_ALL to compile all dependencies:

IDL> resolve_all, /continue_on_error, skip_routines='envi'

The SKIP_ROUTINES keyword is set to ignore all ENVI routines, compiling only IDL routines. The ignored ENVI routines are supplied by ENVI when the extension is executed. Finally, with the SAVE procedure, dump all compiled routines from the IDL session to a file:

IDL> save, /routines, filename='envi5_extension_ex.sav'

The resulting IDL SAVE file envi5_extension_ex.sav can be dropped into one of the extension directories described above. On startup, ENVI will find the file, unpack the extension contained within it, and display it in the Toolbox. This works in both ENVI and ENVI+IDL! Grab the source code and SAVE file for this example here. Update: I modified the call to RESOLVE_ALL above to include the SKIP_ROUTINES keyword on advice from Adam O'Connor (thanks, Adam!); this not only prevents any ENVI routines from being included in the SAVE file, but also makes the SAVE file agnostic to the ENVI version.