跳转至

处理具有未知输出数量(Cardinality)的 ENVITasks

原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/making-envitasks-with-unknown-output-cardinality-work

6033 评价此文章:

暂无评分

处理具有未知输出数量(Cardinality)的 ENVITasks

匿名 2015年8月20日,星期四

正如我之前博文所述,将您的算法包装在 ENVITasks 中可以是一个相对直接的过程。我特别喜欢使用不指定输出产品 URI 的选项,让系统为我生成临时文件名。但此功能有一个重要的注意事项:当您的输出参数是一个动态数组(例如 ENVIRaster[*])时,它不会正确工作,这也会使输入 URI 参数成为 ENVIURI[*] 类型的动态数组。在这种情况下,系统将不知道需要多少个临时文件名,因此只会生成 1 个,这很可能导致您的任务失败。

现在您可能会问,一个不知道会生成多少输出项的任务该如何设计呢?但这并不像听起来那么牵强。我们可能希望构建一个简单的任务,它接收一个多波段的 ENVIRaster,然后导出单独的单波段栅格,或者将其分割成指定数量的瓦片。如果您想测试另一个任务对全局统计与局部统计的敏感性,可以创建一个输出一组不同随机空间子集的任务,以便查看在相同输入但不同周边环境下是否得到类似的输出。在这些情况下,输出的 ENVIRasters 数量可能因输入的 ENVIRaster 和其他参数值而异。关键因素是输出参数并非静态定义的数组大小。

让我们看看最简单且最确定的情况——将多波段栅格切片为一系列单波段栅格。以下是任务模板:

{     "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."         }     ] }

以及该过程(procedure)的 PRO 代码:

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

如果我们尝试在不指定任何输出文件名的情况下运行此任务,它将失败,因为 ENVITask 框架在遇到 ENVIURI[*] 输入参数时只会生成一个文件名:

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$

因此,我们需要手动生成适当数量的文件名,在此例中为 4 个:

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

关键在于,我们需要任务在 Execute 被调用之前就知道输出参数的基数(cardinality)——这是系统知道需要生成多少个文件名的唯一方法。在 INPUT_RASTER 参数的值被设置为 ENVIRaster 之前,我们无法知道这一点,因此我们有一个很小的机会窗口——在 SetProperty 之后,Execute 之前。唯一的选项是利用任务本身是一个对象这一事实,并使用多态性来重写 SetProperty,并在可能时更新 OUTPUT_RASTER_URI 参数。

如果我们查看任务模板,会发现它使用的是 ENVITaskFromProcedure 类,因此这将是我们继承的类。框架使用任务名称来定义任务对象的类,因此我们将遵循该模式,将子类命名为 ENVIRasterBandSliceTask。以下是该类的简化版本:

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   ; 调用基类实现以确保一切正常   self.enviTaskFromProcedure::SetProperty, _EXTRA=refExtra     ; 获取 input_raster 参数对象以查看其是否具有有效值   inputRasterParam = self.Parameter('INPUT_RASTER')   ; 如果值不是有效的 objRef,则返回,因为我们需要查询其属性   if (~Obj_Valid(inputRasterParam.Value)) then return     ; 获取 output_raster_uri 参数,以便我们可以更新其 TYPE 属性   outURIParam = self.Parameter('OUTPUT_RASTER_URI')   ; 将新类型定义为静态大小的 ENVIURI 数组   newType = 'ENVIURI[' + StrTrim(inputRasterParam.Value.nBands,2) + ']'   ; 使用未记录的 _SetProperty 来更新 TYPE 属性   outURIParam._SetProperty, TYPE=newType end   pro enviRasterBandSlicerTask__define   compile_opt idl2, hidden     void = {enviRasterBandSlicerTask,       $           inherits enviTaskFromProcedure  $          } end

这个类相当标准化——继承自 ENVITaskFromProcedure,并具有透传的 Init() 和 Cleanup 方法。唯一的特殊代码在 SetProperty 中,我们首先调用基类实现,然后检查 INPUT_RASTER 参数是否已将其值设置为有效的 ENVIRaster。如果存在有效的 ENVIRaster 值,则我们使用其 NBANDS 属性创建一个新的 ENVIURI 数组类型定义,并将其设置在 OUTPUT_RASTER_URI 参数上。正如我在代码中注释的那样,我们必须使用 ENVITaskParameter::_SetProperty 方法来更新 TYPE 属性。这是一个未记录的方法,内部用于设置通常是不可变的属性值,但在此情况下我们需要使用它。

唯一需要的另一个更改是更新任务模板,将 "baseClass" 值指定为 ENVIRasterBandSlicerTask 而不是 ENVITaskFromProcedure。我们不需要对 RasterBandSlicer 过程进行任何更改,因为我们根本没有更改参数定义。但现在该任务将在桌面 API 中工作,而无需设置 OUTPUT_RASTER_URI,并且在 ESE 中也能工作。

高级 LiDAR 分析改进城市基础设施管理 深入探讨 SAR 数据