X

NV5 Geospatial Blog

Each month, NV5 Geospatial posts new blog content across a variety of categories. Browse our latest posts below to learn about important geospatial information or use the search bar to find a specific topic or author. Stay informed of the latest blog posts, events, and technologies by joining our email list!



Using ENVI and IDL Agents with Your Own API Keys

Using ENVI and IDL Agents with Your Own API Keys

6/22/2026

Earlier this year, we introduced the ENVI® Agent and IDL® Agent to bring intelligent, AI-driven automation to your geospatial and data science workflows. If you missed the launch, you can catch up on the full breakdown by watching our release webinar. Both agents are built upon GitHub Copilot, a powerful AI orchestration... Read More >

What We're Looking Forward to at Esri UC 2026

What We're Looking Forward to at Esri UC 2026

6/16/2026

Every year, the Esri User Conference brings together thousands of geospatial professionals to explore new technologies, share ideas, and learn how organizations are solving complex challenges with GIS. For many members of the NV5 team, attending Esri UC is an annual tradition. Some have attended for more than 15 years. Others will be... Read More >

New ENVI Agent, IDL Agent, and GeoAgent Quick Guides

New ENVI Agent, IDL Agent, and GeoAgent Quick Guides

6/9/2026

The recent release of ENVI® Agent, IDL® Agent, and GeoAgent™ revolutionize how users interact with geospatial software. These agentic AI applications act as partners to plan, simplify, and execute complex workflows. Knowing where to start can be challenging for new users. To this end, we developed three new quick guides to... Read More >

Introducing NISAR Data Support

Introducing NISAR Data Support

6/5/2026

The release of ENVI® SARscape 6.3 in April 2026 includes preliminary support for NASA-ISRO SAR (NISAR) data. The NISAR mission is a joint Earth-observing satellite project between NASA and the Indian Space Research Organization designed to monitor changes in the planet’s land and ice surfaces using advanced radar imaging. It... Read More >

Monitoring Illegal Mining in the Amazon: Turning Persistent Data Into Actionable Insight

Monitoring Illegal Mining in the Amazon: Turning Persistent Data Into Actionable Insight

5/28/2026

Illegal mining over decades has constituted one of the most persistent and complex socio-environmental problems in the Brazilian Amazon. In recent years, with the increasingly intensive use of mechanized extraction, the associated environmental impacts—such as deforestation, intense soil disturbance, river siltation, and mercury... Read More >

1345678910Last
7110 Rate this article:
No rating

Making ENVITasks with Unknown Output Cardinality Work

Anonym

As I’ve blogged about before, wrapping your algorithms in ENVITasks can be a relatively straightforward process.  I especially enjoy using the option of not specifying my output product URIs and letting the system generate temporary filenames for me.  But there is a big caveat with this feature, which is that it won’t behave correctly when your output parameter is a dynamic array (ENVIRaster[*], for example), which makes the input URI parameter also a dynamic array of type ENVIURI[*].  In this case the system will not know how many temporary filenames are needed, so it will only generate 1, which will likely make your task fail.

Now you might be asking how a task could be designed that doesn’t know how many output item it generates, but it isn’t as far-fetched as it sounds.  We might want to build a simple task that takes in a single multi-band ENVIRaster and then exports separate single-band rasters, or chops it up into a specified number of tiles. If you wanted to test another task for its sensitivity to global vs local statistics, you could have a task that outputs a set of different random spatial subsets, so you can see if you get similar outputs for the same inputs with different surroundings.  In each of these cases the number of output ENVIRasters can vary depending on the input ENVIRaster and other parameter values.  The key factor is that if the output parameter is not a statically defined array size.

Let’s look at the simplest and most deterministic case – slicing a multi-band raster into a collection of single-band rasters.  Here is the task template:

{
    "name": "RasterBandSlicer",
    "baseClass": "ENVITaskFromProcedure",
    "routine": "RasterBandSlicer",
    "displayName": "ENVIRaster Band Slicer",
    "description": "This task takes an input raster and exports each band as a separate output raster.",
    "version": "5.3",
    "invocationType": "keywords",
    "parameters": [
        {
            "name": "INPUT_RASTER",
            "keyword": "INPUT_RASTER",
            "displayName": "Input Raster",
            "dataType": "ENVIRASTER",
            "direction": "input",
            "parameterType": "required",
                "description": "Specify the raster to slice into separate bands."
        },
        {
            "name": "OUTPUT_RASTER",
            "keyword": "OUT_FILENAMES",
            "displayName": "Output Rasters",
            "dataType": "ENVIRASTER[*]",
            "direction": "output",
            "parameterType": "required",
            "description": "This is an array of output rasters of filetype ENVI."
        }
    ]
}

And the PRO code for the procedure:

pro RasterBandSlicer, INPUT_RASTER=inputRaster, OUT_FILENAMES=outFilenames
  compile_opt idl2
 
  nBands = inputRaster.nBands
 
  if (N_Elements(outFilenames) ne nBands) then begin
    Message, 'Invalid OUT_FILENAMES, must have ' + StrTrim(nBands,2) + ' elements'
  endif
 
  for i = 0, nBands-1 do begin
    subRaster = ENVISubsetRaster(inputRaster, BANDS=i)
    subRaster.Export, outFilenames[i], 'ENVI'
  endfor
end

If we try to run this task without specifying any output filenames, it will fail because the ENVITask framework will only generate one filename when it encounters the ENVIURI[*] input parameter:

IDL> nv = ENVI(/HEADLESS)
IDL> file = FilePath('qb_boulder_msi', ROOT_DIR=nv.ROOT_DIR, SUBDIR='data')
IDL> oRaster = nv.OpenRaster(file)
IDL> oTask = ENVITask('RasterBandSlicer')
IDL> oTask.Input_Raster = oRaster
IDL> oTask.Execute
% RASTERBANDSLICER: Invalid OUT_FILENAMES, must have 4 elements
% Execution halted at: $MAIN$     

So we need to manually generate the appropriate number of filenames, 4 in this case:

IDL> nv = ENVI(/HEADLESS)
IDL> file = FilePath('qb_boulder_msi', ROOT_DIR=nv.ROOT_DIR, SUBDIR='data')
IDL> oRaster = nv.OpenRaster(file)
IDL> oTask = ENVITask('RasterBandSlicer')
IDL> oTask.Input_Raster = oRaster
IDL> tmp = nv.GetTemporaryFilename('')
IDL> oTask.Output_Raster_URI = [ tmp+'-band0.dat', tmp+'-band1.dat', $
                                 tmp+'-band2.dat', tmp+'-band3.dat']
IDL> oTask.Execute
ENVI> print, oTask.Output_Raster
<ObjHeapVar223720(ENVIRASTER)><ObjHeapVar223723(ENVIRASTER)><ObjHeapVar223726(ENVIRASTER)><ObjHeapVar223729(ENVIRASTER)>

The trick is that we need the task to know the cardinality of the output parameters before Execute is called on it - it’s the only way the system will know how many filenames to generate.  We can’t know this until the INPUT_RASTER parameter has its value set to an ENVIRaster, so we have a small window of opportunity – after SetProperty and before Execute.  The only option is to take advantage of the fact that the task is an object and use polymorphism to override SetProperty and update the OUTPUT_RASTER_URI parameter when we can.

If we look at the task template, we see that it is using the ENVITaskFromProcedure class, so that is the class we will inherit from.  The framework uses the task name to define the class of the task object, so we will follow that pattern and call the subclass ENVIRasterBandSliceTask.  Here is a barebones version of this class:

function enviRasterBandSlicerTask::Init, _REF_EXTRA=refExtra
  compile_opt idl2, hidden
 
  return, self.enviTaskFromProcedure::Init(_EXTRA=refExtra)
end
 
pro enviRasterBandSlicerTask::Cleanup
  compile_opt idl2, hidden
 
  self.enviTaskFromProcedure::Cleanup
end
 
pro enviRasterBandSlicerTask::SetProperty, _REF_EXTRA=refExtra
  compile_opt idl2, hidden
  ; call base class implementation to make sure everything's okay
  self.enviTaskFromProcedure::SetProperty, _EXTRA=refExtra
 
  ; get the input_raster parameter object to see if it has a valid value
  inputRasterParam = self.Parameter('INPUT_RASTER')
  ; if value is not a valid objRef, then return, since we need to query it's properties
  if (~Obj_Valid(inputRasterParam.Value)) then return
 
  ; get output_raster_uri parameter, so we can update its TYPE property
  outURIParam = self.Parameter('OUTPUT_RASTER_URI')
  ; define new type as statically sized array of ENVIURI
  newType = 'ENVIURI[' + StrTrim(inputRasterParam.Value.nBands,2) + ']'
  ; use undocumented _SetProperty to update the TYPE property
  outURIParam._SetProperty, TYPE=newType
end
 
pro enviRasterBandSlicerTask__define
  compile_opt idl2, hidden
 
  void = {enviRasterBandSlicerTask,       $
          inherits enviTaskFromProcedure  $
         }
end

The class is pretty boilerplate – inherit from ENVITaskFromProcedure and have passthrough Init() and Cleanup methods.  The only special code is in SetProperty, where we first call the base class implementation and then check if the INPUT_RASTER parameter has had its value set to a valid ENVIRaster yet or not.  If there is a valid ENVIRaster value, then we use its NBANDS property to create a new ENVIURI array type definition and set it on the OUTPUT_RASTER_URI parameter.  As I commented in the code, we have to use the ENVITaskParameter::_SetProperty method to update the TYPE property.  This is an undocumented method that is used internally to set what are normally immutable property values, but in this case we need to use it.

The only other change needed is to update the task template to specify ENVIRasterBandSlicerTask as the “baseClass” value instead of ENVITaskFromProcedure.  We don’t have to make any changes to the RasterBandSlicer procedure, since we didn’t change the parameter definitions at all.  But now the task will work in the desktop API without setting OUTPUT_RASTER_URI, and it works in ESE.

Please login or register to post comments.