跳转至

支持进度显示与中止功能的用户自定义 ENVITask

原文链接:https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/user-defined-envitasks-with-progress-and-abort

12955 评价本文:

未评分

支持进度显示与中止功能的用户自定义 ENVITask

匿名 2015年7月16日,星期四

ENVI 5.3 即将发布。虽然我们新增了55个任务,使总数达到137个,但我认为更令人兴奋的是新增的 ENVIBroadcastChannel 和 ENVIAbortableTaskFromProcedure 类。前者允许用户自定义的 ENVITask 显示并更新进度对话框,后者则提供了在用户点击取消按钮时通知任务的机制。这是对我上一篇博客文章的扩展,该文章介绍了在 ENVI 5.2 SP1 中引入的用户自定义 ENVITask。

ENVIBroadcastChannel 是一个新的发布/订阅框架的基础。对象可以订阅一个频道,然后接收由系统任何其他部分广播的所有 ENVIMessage 对象的通知。您可以创建自己的广播频道,但为了进度对话框的目的,我们添加了一个作为 ENVI 对象的成员。您可以通过调用 ENVI::GetBroadcastChannel() 函数来访问它,然后在此频道上广播 ENVIStartMessage、ENVIProgressMessage 和 ENVIFinishMessage 对象将分别显示、更新或隐藏进度对话框。

这些消息类都使用一个对象引用作为关联标识符,因此您需要在过程中构建每个消息时使用相同的对象。在不支持任务取消的情况下,只要保持一致,我们可以使用任何对象引用。如果您省略消息构造函数的这个参数,它们会抛出“您必须提供一个有效的源对象”的消息。在我的示例中,我使用了 ENVI 对象,但选择权在您。

我们必须首先广播一个 ENVIStartMessage,传入一个消息字符串和源对象引用。此处使用的消息字符串将是进度对话框的标题。随着过程在数据中逐步推进,您可以随意广播任意多个 ENVIProgressMessage 事件。您可以为每次广播创建一个新的消息对象,或者只需更新其 PERCENT 属性并每次都广播同一个实例。ENVIProgressMessage 类的第一个构造函数参数是一个消息字符串,它显示在进度对话框中进度条的上方。此参数可作为 MESSAGE 属性访问,但对象一旦创建就不可更改。因此,如果您想更改消息以向用户指示任务的不同阶段,则需要创建单独的进度消息实例。PERCENT 属性应为 [0, 100] 范围内的整数,它告诉进度条用蓝色填充的百分比。当过程完成时,必须广播一个 ENVIFinishMessage 以关闭进度对话框。如果不这样做,将导致进度对话框无法关闭。

以下是显示进度对话框的 BandMathExample 的更新版本。首先是任务模板 BandMathProgressExample.task:

{
  "name": "BandMathProgressExample",
  "baseClass": "ENVITaskFromProcedure",
  "routine": "bandmathprogressexample",
  "displayName": "ENVI 波段运算进度示例",
  "description": "这是一个在两张栅格上执行带进度显示的波段运算的自定义任务示例。",
  "version": "5.3",
  "parameters":[
    {
      "name": "INPUT_RASTER1",
      "displayName": "输入栅格 1",
      "dataType": "ENVIRASTER",
      "direction": "input",
      "parameterType": "required",
      "description": "指定在其上执行波段运算的第一个栅格。此栅格用于确定输出的数据类型。"
    },
    {
      "name": "INPUT_RASTER2",
      "displayName": "输入栅格 2",
      "dataType": "ENVIRASTER",
      "direction": "input",
      "parameterType": "required",
      "description": "指定在其上执行波段运算的第二个栅格。"
    },
    {
      "name": "OUTPUT_RASTER",
      "displayName": "输出栅格",
      "dataType": "ENVIRASTER",
      "direction": "output",
      "parameterType": "required",
      "description": "这是对文件类型为 ENVI 的输出栅格的引用。"
    }
  ]
}

然后是过程:

pro BandMathProgressExample, INPUT_RASTER1=raster1, $
                             INPUT_RASTER2=raster2, $
                             OUTPUT_RASTER_URI=outputURI
compile_opt idl2

e = ENVI(/CURRENT)

;获取广播频道
  oChannel = e.GetBroadcastChannel()

outputRaster = ENVIRaster(URI=outputURI, INHERITS_FROM=raster1)

; 创建迭代器
  rasterTileIterator1 = raster1.CreateTileIterator()

; 确定进度的步骤数
  ; 瓦片数量加上一步用于保存
  nSteps = rasterTileIterator1.NTILES + 2
  oStartMessage = ENVIStartMessage('波段运算', e)
  oChannel.Broadcast, oStartMessage
  oProgressMessage = ENVIProgressMessage('正在执行波段运算', $
                     0, e)
  oChannel.Broadcast, oProgressMessage

;遍历数据
foreach rasterTile1, rasterTileIterator1, stepIndex do begin
    ; 广播进度
    oProgressMessage.Percent = stepIndex*100.0/nSteps
    oChannel.Broadcast, oProgressMessage

; 从第二个栅格检索瓦片,第一个栅片由 foreach 处理
    rasterTile2 = raster2.GetData(BANDS=rasterTileIterator1.CURRENT_BAND, $
                  SUB_RECT=rasterTileIterator1.CURRENT_SUBRECT)

; 将输出瓦片设置为栅格1瓦片减去栅格2瓦片
    outputRaster.SetTile, rasterTile1 - rasterTile2, rasterTileIterator1
endforeach

; 数据最终化
  outputRaster.Save

oProgressMessage.Percent = 100
  oChannel.Broadcast, oProgressMessage

; 广播完成
  oFinishMessage = ENVIFinishMessage(e)
  oChannel.Broadcast, oFinishMessage
end

这个示例将显示一个进度对话框,但由于我没有使用 ENVIAbortable 对象创建消息对象,所以对话框上没有取消按钮。为了获得取消按钮,我们需要使用 ENVIAbortableTaskFromProcedure。ENVIAbortableTaskFromProcedure 类继承自 ENVITaskFromProcedure,因此在编写过程时所有规则仍然适用。但现在有一条新规则 —— 您必须在过程中添加 ABORTABLE 关键字,否则在尝试执行任务时会抛出错误。此关键字无需在任务模板中定义,它会由任务对象自动分配,传入一个您可以查询的 ENVIAbortable 对象。如果用户点击进度对话框上的取消按钮,那么这个 ENVIAbortable 对象的 ABORT_REQUESTED 属性将被设置为 true。取消按钮不会生成中断事件,因为我们无法确定您的过程是否处于可以轻松清理的状态,因此您作为过程的实现者有责任在适当时机查询可中止对象,以判断是否应该清理并返回,而不是继续处理。

您会注意到,在下面的示例中,对可中止对象的 ABORT_REQUESTED 状态的测试是在进度广播之后进行的,而不是之前。这是因为该属性是在处理该进度消息的过程中更新的,所以如果我们在广播进度之前检查该属性,我们会在意识到取消按钮被按下之前额外执行一次 for 循环迭代。

以下是支持取消功能的增强版本。首先是任务模板 BandMathProgressAbortExample.task:

{
  "name": "BandMathProgressAbortExample",
  "baseClass": "ENVIAbortableTaskFromProcedure",
  "routine": "bandmathprogressabortexample",
  "displayName": "ENVI 波段运算进度示例",
  "description": "这是一个在两张栅格上执行带进度显示的波段运算的自定义任务示例。",
  "version": "5.3",
  "parameters":[
    {
      "name": "INPUT_RASTER1",
      "displayName": "输入栅格 1",
      "dataType": "ENVIRASTER",
      "direction": "input",
      "parameterType": "required",
      "description": "指定在其上执行波段运算的第一个栅格。此栅格用于确定输出的数据类型。"
    },
    {
      "name": "INPUT_RASTER2",
      "displayName": "输入栅格 2",
      "dataType": "ENVIRASTER",
      "direction": "input",
      "parameterType": "required",
      "description": "指定在其上执行波段运算的第二个栅格。"
    },
    {
      "name": "OUTPUT_RASTER",
      "displayName": "输出栅格",
      "dataType": "ENVIRASTER",
      "direction": "output",
      "parameterType": "required",
      "description": "这是对文件类型为 ENVI 的输出栅格的引用。"
    }
  ]
}

然后是过程:

pro BandMathProgressAbortExample, INPUT_RASTER1=raster1, $
                                  INPUT_RASTER2=raster2, $
                                  ABORTABLE=abortable, $
                                  OUTPUT_RASTER_URI=outputURI

compile_opt idl2

  e = ENVI(/CURRENT)

  ;获取广播频道
  oChannel = e.GetBroadcastChannel()

  outputRaster = ENVIRaster(URI=outputURI, INHERITS_FROM=raster1)

  ; 创建迭代器
  rasterTileIterator1 = raster1.CreateTileIterator()

  ; 确定进度的步骤数
  ; 瓦片数量加上一步用于保存
  nSteps = rasterTileIterator1.NTILES + 2
  oStartMessage = ENVIStartMessage('波段运算', abortable)
  oChannel.Broadcast, oStartMessage
  oProgressMessage = ENVIProgressMessage('正在执行波段运算', $
                                            0, abortable)
  oChannel.Broadcast, oProgressMessage

  ;遍历数据
foreach rasterTile1, rasterTileIterator1, stepIndex do begin
    ; 广播进度
    oProgressMessage.Percent = stepIndex*100.0/nSteps
    oChannel.Broadcast, oProgressMessage

    ;发送进度后检查是否中止,以查看 Abort_Requested 是否已被任何监听器设置
if (abortable.ABORT_REQUESTED) then begin
      outputRaster.close
return
endif

    ; 从第二个栅格检索瓦片,第一个栅片由 foreach 处理
    rasterTile2 = raster2.GetData(BANDS=rasterTileIterator1.CURRENT_BAND, $
                  SUB_RECT=rasterTileIterator1.CURRENT_SUBRECT)

    ; 将输出瓦片设置为栅格1瓦片减去栅格2瓦片
    outputRaster.SetTile, rasterTile1 - rasterTile2, rasterTileIterator1
endforeach

  ; 数据最终化
  outputRaster.Save

  oProgressMessage.percent = 100
  oChannel.Broadcast, oProgressMessage

  ; 广播完成
  oFinishMessage = ENVIFinishMessage(abortable)
  oChannel.Broadcast, oFinishMessage
end

数据产品等级快速入门 在 ENVI 中处理 WorldView-3 SWIR 数据及新视野号视角下的冥王星