User-Defined ENVITasks in ENVI 5.2 SP1
ENVI 5.2 SP1 has just been released, so I'm back with another blog post about the new features we have added to the ENVITask API. We have added 24 new tasks, to take the total to 82, but the most exciting feature is that you our customers now have the tools to create your own ENVITasks. We have made good progress on exposing ENVI's analytic capabilities as tasks, but there are so many more of you than there are of us, with a myriad of ideas and algorithms that there's no way we could satisfy all your needs if we didn't make it easy for you to define your own tasks. Enter the new ENVITaskFromProcedure class, which allows you to wrap almost any IDL procedure as an ENVITask.
Before I go into detail on how to use ENVITaskFromProcedure,allow me to convince you why you would want to go through the minimal effort in creating a task for your given procedure.
First and foremost is the fact that once you have your ENVITask working in desktop ENVI, it is trivial to copy the files onto an ENVI Services Engine server and get your task working in the cloud. The ESE server doesn't care if an ENVITask is part of the ENVI installation or a custom task created by you, it will just work.
Another valuable feature of ENVITask is the automatic parameter validation that it provides. You don't need to check the type and cardinality of every input to your procedure, you define that in the task template and the ENVITaskParameter class takes care of all that work for you. This may not help the existing code you wrap as much, but as you write new algorithms with the intent of wrapping them as ENVITasks, you'll be amazed how much shorter and cleaner your code will be. The ENVITask framework allows you to treat IDL like a strongly typed language, which frees you to focus on algorithm implementation details and not robustness in the face of bad inputs.
As I mentioned in my last ENVITask blog post, in ENVI 5.2 we introduced the dynamic UI for ENVITasks. So you don't need to write custom widget code for each algorithm, you can just invoke the ENVIUI::SelectTaskParameters() function to pop up a dialog to enter all the input parameters for your task.
Hopefully I've at least piqued your interest in looking at ENVITaskFromProcedure, the next obvious question is "what rules does my procedure need to follow to be wrapped as a task?". There are a couple, but they aren't too burdensome, and there are easy ways to create a wrapper procedure to fix any violations. Here are the rules, and the wrapper workarounds:
1. Only keywords are allowed, no positional parameters.
Keywords enforce a more explicit mapping between input values and intention. If we had written the Gram-Schmidt Pan Sharpening task to have a single input parameter that was a 2-element array of ENVIRaster objects, then you need to read the documentation to figure out if it [ Pan, MSI ] or [ MSI, Pan ]. By having two separate scalar parameters named INPUT_LOW_RESOLUTION_RASTER and INPUT_HIGH_RESOLUTION_RASTER, the task is self-documenting and it's much harder to confuse which input is which.
If you have a procedure with positional parameters that you want to wrap as a task, all you need to do is create a new procedure that has only keywords, and then create a keyword for each positional parameter. This wrapper procedure then invokes the original procedure, passing in the keywords in the appropriate order for the positional parameters.
2. The algorithm must be a procedure, not a function.
The return value of a function is a similar to the positional parameters, you can think of it as the parameter at index -1. We can't know what the type of this value is, or what you might want to name it, so we don't try. The solution to this is the same as the positional parameter case, create a wrapper procedure that calls the function and stores the function's return value in an output keyword.
3. The procedure must use ENVI5 API objects such as ENVIRaster, not ENVI Classic FIDs.
ENVI was around for quite a while before we released ENVI 5.0, and I'm sure there are many routines out there written using its API of FID, DIMS, POS. This code doesn't have to be rewritten, it is easy to create a wrapper procedure that can translate an ENVIRaster input into the values needed for those keywords:
fid = ENVIRasterToFid(inRaster)
dims = [ -1, 0, inRaster.nColums-1, 0, inRaster.nRows-1 ]
pos = Lindgen(inRaster.nBands)
That's pretty much it, if your procedure obeys these rules, or is wrapped by one that does, then you can wrap it in an ENVITask. As the help docs for Custom Tasks describes, you create your .task template file, make sure the"baseClass" attribute is set to "ENVITaskFromProcedure", and the "routine" attribute is set to your procedure name. A good starting point for this is the BandMathExample that we included in the ENVI 5.2 SP1 release. On Windows you can find it in the "C:\Program Files\Exelis\ENVI52\examples\tasks\bandmathexample" folder. This example includes the procedure beingwrapped, the .task template file, some PRO code to create a user extension forthe ENVI toolbox, and a PRO file to test the task.