Extending ENVI Extensions
Anonym
One of my favorite parts of being able to extend ENVI with IDL is that I have the ability to add custom buttons into ENVI's toolbox. These buttons are called extensions and I have made many of them during my time with NV5 Geospatial. I have created buttons for our tradeshow demos, examples for customers, and even for the Precision Ag Toolkit. Extensions in ENVI can you anything that you want them to. Most of the time this is using ENVI's dynamic UI for creating simple widgets that allow a user to select all the inputs and outputs for a task, but you can also just call IDL directly and instantiate some other GUI based application if you want.
While extensions are very easy to make (I just copy/paste old code I had and tweak it), they can be cumbersome when you have many extensions to create all at once. In addition to this, if you have some addition that you want added to all your extensions, then you have to go through each one by hand to change them. This is where getting creative with IDL programming comes in really handy because IDL can be used to automate the generation of buttons in ENVI. For this example, I decided to finally take a crack at dynamically creating X number of buttons from ENVI tasks without needing to write a separate extension for every one.
The basic idea is that I wanted a dynamic extension that would allow me to pass in any task, create a button, run the right task when I press the button, and offer the ability to display any and all results from the task that are EVNIRasters, ENVIROIs, or ENVIVectors (or shapefiles). If you're unfamiliar how to add extensions to ENVI, then here is a link to the docs which can provide some background information
http://www.nv5geospatialsoftware.com/docs/ENVI__AddExtension.html
Following the example in the docs, the way that I decided to proceed was to use the UVALUE for the buttons to pass in information about which task I wanted to create a dynamic UI for. I decided to use an orderedhash for my data structure with: the key is the task name, the value is a two element string array that represents the folder that we want the task to appear in the name of the button that you will see in ENVI's toolbox. Here is an example of what the data structure looked like:
;create an orderedhash of buttons that we want to create
buttons = orderedhash()
buttons['ROIMaskRaster'] = ['/Regions of Interest', 'ROI Mask Raster']
At this point it just came down to looping over the hash correctly so that the buttons would be made in the right space. I chose to use the foreach loop because it makes it easy to get the key and value from hashes or orderedhashes all at the same time. Here is what that code looks like:
foreach val, buttons, key do begin
e.AddExtension, val[1], 'better_extensions', $
PATH = '/Better Extensions' + val[0], $
UVALUE = key
endforeach
That short code block will dynamically go through every entry in my orderedhash and then create the buttons with the right placement and task to be executed when clicked on. Note that I created a subfolder called 'Better Extensions' that would contain all the fancy buttons that the extension would create. Once I had this code figured out, I just needed to look at the actual procedure which would run the extension (I called it "better_extensions")
You can see the complete code for better_extensions below, but there are a few key points to mention about the code:
-
To get the task name for the button that was clicked, when creating the extension I used the UVALUE keyword to store the name. Then, in the better_extensions procedure, I get the event as an argument and can get the uvalue for the button using widget_control
.
-
To create the task UI all I needed to do was the following:
;create a dialogue for the rest of the task
ok = e.UI.SelectTaskParameters(task)
-
After that, I just needed to go through each task parameter, check for INPUT or OUTPUT and, if it was output, check for ENVIRaster, ENVIVector, or ENVIROI. I then saved these and displayed them in ENVI's current view if they were found in the output.
The main goal here was to show that, with a little bit of IDL programming, you can take your ENVI analytics to the next level. The code for the extension can be found below. Cheers!
Also, keep an eye out for my next blog where I'm going to talk about giving an old extension (from the online extensions library) a much needed update!
; Add the extension to the toolbox. Called automatically on ENVI startup.
pro better_extensions_extensions_init
; Set compile options
compile_opt idl2
; Get ENVI session
e = envi(/CURRENT)
;create an orderedhash of buttons that we want to create
buttons = orderedhash()
;sample ROI tools
buttons['ROIMaskRaster'] = $
['/Regions of Interest', 'ROI Mask Raster']
bttons['ROIToClassification'] = $
['/Regsions of Interest', 'Classification Image from ROis']
;classification tools
buttons['ISODataClassification'] = $
['/Classification/Unsupervised Classification', 'ISOData Classification']
buttons['ClassificationAggregation'] = $
['/Classification/Post Classification', 'Classification Aggregation']
buttons['ClassificationClumping'] = $
['/Classification/Post Classification', 'Clump Classes']
buttons['ClassificationSieving'] = $
['/Classification/Post Classification', 'Sieve Classes']
buttons['ClassificationSmoothing'] = $
['/Classification/Post Classification', 'Classification Smoothing']
buttons['ClassificationToShapefile'] = $
['/Classification/Post Classification', 'Classification to vector']
;radiometric correction tools
buttons['DarkSubtractionCorrection'] = $
['/Radiometric Correction', 'Dark Subtraction']
buttons['ApplyGainOffset'] = $
['/Radiometric Correction', 'Apply Gain and Offset']
;filters
buttons['BitErrorAdaptiveFilter'] = $
['/Filters', 'Bit Errors Filter']
buttons['GaussianHighPassFilter'] = $
['/Filters', 'Gaussian High Pass Filter']
buttons['GaussianLowPassFilter'] = $
['/Filters', 'Gaussian Low Pass Filter']
buttons['HighPassFilter'] = $
['/Filters', 'High Pass Filter']
buttons['LowPassFilter'] = $
['/Filters', 'Low Pass Filter']
;add the buttons
foreach val, buttons, key do begin
e.AddExtension, val[1], 'better_extensions', $
PATH = '/Better Extensions' + val[0], $
UVALUE = key
endforeach
end
; ENVI Extension code. Called when the toolbox item is chosen.
pro better_extensions, event
; Set compile options
compile_opt idl2
;Get ENVI session
e = envi(/CURRENT)
if (e eq !NULL) then begin
e = envi()
endif
CATCH, Error_status
IF Error_status NE 0 then begin
catch, /CANCEL
help, /LAST_MESSAGE, output = err_txt
p = dialog_message(err_txt)
return
ENDIF
;get the directory that our extension lives in
WIDGET_CONTROL, event.id, GET_UVALUE = taskName
;create the task object
task = ENVITask(taskname)
;add parameter to ask if we want to displayt he results
displayParam = ENVITaskParameter(NAME='DISPLAY_RESULT', $
DISPLAY_NAME = 'Display Result',$
DEFAULT = !FALSE,$
TYPE='bool', $
DIRECTION='input', $
REQUIRED = 1)
;replace old parameter
task.AddParameter, displayParam
;create a dialogue for the rest of the task
ok = e.UI.SelectTaskParameters(task)
;user selected OK
if (ok eq 'OK') then begin
;get rid of dummy result
display = task.DISPLAY_RESULT
task.RemoveParameter, 'DISPLAY_RESULT'
;run the task
task.execute
;check if we want to display things
if display then begin
;things to display
rasters = list()
rois = list()
shapefiles = list()
;check for output datatypes that are rasters, vectors, or rois
foreach paramName, task.ParameterNames() do begin
param = task.parameter(paramName)
if (param.direction eq 'OUTPUT') then begin
if (param.TYPE eq 'ENVIRASTER') then begin
e.data.add, param.VALUE
rasters.add, param.VALUE
endif
if (param.TYPE eq 'ENVIROI') then begin
e.data.add, param.VALUE
rois.add, param.VALUE
endif
if (param.TYPE eq 'ENVIVECTOR') then begin
e.data.add, param.VALUE
shapefiles.add, param.VALUE
endif
endif
endforeach
;get envi's view
View1 = e.GetView()
;disable refresh
e.refresh, /DISABLE
;check for rasters and ROIs
if (n_elements(rasters) gt 0) then begin
;display each raster in the view
foreach raster, rasters do begin
rasterLayer = View1.CreateLayer(raster)
endforeach
;only display roi is there is a rasterlayer
foreach roiarr, rois do begin
;handle arrays of rois
foreach roi, roiarr do begin
roilayer = rasterlayer.AddROI(roi)
endforeach
endforeach
endif
;check for vectors
;only display vectors if ROI is present
foreach vector, shapefiles do begin
; Create a vector layer
vectorLayer = view.CreateLayer(vector)
endforeach
;refresh display again
e.refresh
endif
endif
end