处理具有未知输出数量(Cardinality)的 ENVITasks
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 中也能工作。