【问题标题】:arcpy + multiprocessing: error: could not save Raster datasetarcpy + 多处理:错误:无法保存栅格数据集
【发布时间】:2016-08-18 07:46:39
【问题描述】:

我正在使用arcpy 对栅格进行一些数字运算,并希望使用multiprocessing 包来加快速度。基本上,我需要遍历一个元组列表,使用每个元组进行一些栅格计算并将一些输出写入文件。我的输入包括一个数据栅格(测深)、一个定义区域的栅格和一个由两个浮点数组成的元组(水面高程、深度)。我的程序包含一个函数computeplane,它接受一个元组并运行一系列栅格计算以生成五个栅格(总、沿海、表面、次表面、深部),然后为每个这些栅格调用函数processtable使用arcpy.sa.ZonalStatisticsAsTable将值写入dbf,使用arcpy.AddField_management添加一些字段,使用arcpy.TableToTable_conversion将dbf转换为csv,最后使用arcpy.Delete_management删除dbf文件。

基于一些other SO posts,我已经将我的代码封装在main() 中,这样multiprocessing.Pool 应该会很好玩。我使用main() 创建元组集,使用pool.map 进行多处理。我使用tempfile 包为dbf 文件选择名称以避免名称冲突;保证 csv 文件名不会与其他线程冲突。

我已经用 for 循环测试了我的代码,它工作正常,但是当我尝试使用 pool.map 时,我得到了

RuntimeError:错误 010240:无法将栅格数据集保存到 C:\Users\Michael\AppData\Local\ESRI\Desktop10.4\SpatialAnalyst\lesst_ras 输出格式为 GRID。

这里发生了什么?此错误不会出现在代码的非多进程版本中,而且我没有在任何地方写出光栅 --- 再说一遍,我没有不知道arcpy 如何处理中间栅格(我当然认为它不会将它们保存在内存中——它们太大了)。我是否需要告诉arcpy 它处理光栅计算以使多处理工作的方式?我在下面包含了我的 python 文件。

import arcpy
arcpy.CheckOutExtension("Spatial")
import arcpy.sa
import numpy
import multiprocessing
import tempfile
bathymetry_path = r'C:/GIS workspace/RRE/habitat.gdb/bathymetry_NGVD_meters'
zones_path = r'C:/GIS workspace/RRE/habitat.gdb/markerzones_meters'
table_folder = r'C:/GIS workspace/RRE/zonetables'
bathymetry = arcpy.sa.Raster(bathymetry_path)
zones = arcpy.sa.Raster(zones_path)

def processtable(raster_obj, zone_file, w, z, out_folder, out_file):
  temp_name = "/" + next(tempfile._get_candidate_names()) + ".dbf"
  arcpy.sa.ZonalStatisticsAsTable(zone_file, 'VALUE', raster_obj, out_folder + temp_name, "DATA", "SUM")
  arcpy.AddField_management(out_folder + temp_name, "wse", 'TEXT')
  arcpy.AddField_management(out_folder + temp_name, "z", 'TEXT')
  arcpy.CalculateField_management(out_folder + temp_name, "wse", "'" + str(w) + "'", "PYTHON")
  arcpy.CalculateField_management(out_folder + temp_name, "z", "'" + str(z) + "'", "PYTHON")
  arcpy.TableToTable_conversion(out_folder + temp_name, out_folder, out_file)
  arcpy.Delete_management(out_folder + temp_name)

def computeplane(wsedepth):
  wse = wsedepth[0]
  depth = wsedepth[1]
  total = bathymetry < depth
  littoral = total & ((wse - bathymetry) < 2)
  surface = total & ~(littoral) & ((total + wse - depth) < (total + 2))
  profundal = total & ((total + wse - depth) > (total + 5))
  subsurface = total & ~(profundal | surface | littoral)
  # zonal statistics table names
  total_name = 'total_w' + str(wse) + '_z' + str(depth) + '.csv'
  littoral_name = 'littoral_w' + str(wse) + '_z' + str(depth) + '.csv'
  surface_name = 'surface_w' + str(wse) + '_z' + str(depth) + '.csv'
  subsurface_name = 'subsurface_w' + str(wse) + '_z' + str(depth) + '.csv'
  profundal_name = 'profundal_w' + str(wse) + '_z' + str(depth) + '.csv'
  # compute zonal statistics
  processtable(total, zones, wse, depth, table_folder, total_name)
  processtable(littoral, zones, wse, depth, table_folder, littoral_name)
  processtable(surface, zones, wse, depth, table_folder, surface_name)
  processtable(profundal, zones, wse, depth, table_folder, profundal_name)
  processtable(subsurface, zones, wse, depth, table_folder, subsurface_name)

def main():
  watersurface = numpy.arange(-15.8, 2.7, 0.1)
  # take small subset of the tuples: watersurface[33:34]  
  wsedepths = [(watersurface[x], watersurface[y]) for x in range(watersurface.size)[33:34] for y in range(watersurface[0:x+1].size)]
  pool = multiprocessing.Pool()
  pool.map(computeplane, wsedepths)
  pool.close()
  pool.join()

if __name__ == '__main__':
  main()

更新

更多调查表明,这与其说是multiprocessing 问题,不如说是 ArcGIS 进行栅格处理的方式的问题。栅格代数结果被写入默认工作空间中的文件;就我而言,我没有指定文件夹,所以arcpy 正在将栅格写入某个 AppData 文件夹。 ArcGIS 根据您的代数表达式使用基本名称,例如 Lessth、Lessth_1 等。由于我没有指定工作区,所有multiprocessing 线程都在写入此文件夹。虽然单个 arcpy 进程可以跟踪名称,但多个进程都尝试写入相同的栅格名称并撞到其他进程的锁。

我尝试在computeplane开头创建一个随机工作区(文件gdb),然后在最后删除它,但是arcpy通常不会及时释放它的锁并且进程在删除时崩溃陈述。所以我不确定如何继续。

【问题讨论】:

    标签: python python-multiprocessing arcpy


    【解决方案1】:

    好吧,ERROR 010240 的解决方案是使用 arcpy.gp.RasterCalculator_sa 函数而不是 arcpy.sa 来编写具有指定输出名称的栅格。不幸的是,重新实现后我遇到了

    致命错误 (INFADI) 缺少目录

    描述为in another stackexchange post。那篇文章中的建议是在每个函数调用中指定一个不同的工作区,这是我尝试的第一件事(没有成功)。还有一些讨论是一次只能写出一个栅格,因此无论如何都没有必要对栅格计算进行多处理。我放弃了!

    【讨论】:

      【解决方案2】:

      我也做了很多工作来制作一个有效的脚本。

      由于您的数据与我习惯的数据略有不同,我无法测试答案,也无法判断它是否有效,但可以说一些话让您对多进程敞开心扉,并为您提出一个新脚本测试。

      我已经看到有些东西可以并行化,有些则不能并行化,或者从多进程中几乎一无所获。

      使用多进程和 arcpy 的良好做法是不使用地理数据库,尝试编写真正的“.tif”文件或“in_memory/tempfile”,然后在计算后删除要并行化的函数内部。一开始不要为空间而烦恼,减少要测试的数据,当它起作用时通过删除临时文件来改进它,在其中放置一些有用的打印。

      回到这个问题,你的脚本中有一些东西不能使用,比如传递一个 arcpy.Raster 对象,而不是你必须将路径传递给栅格并在函数内部读取它。栅格的路径是一个字符串,因此它是 Pickable,这意味着它们可以很容易地传递给一个新进程,并且必须特别注意这一点。

      另外的事情是声明一个像'table_folder'这样的变量,并期望你所有的7个核心核心都回到第一个核心中的原始进程来询问它的路径。 多进程之间不容易共享变量,并且使用原始代码,您必须创建一个独立运行的函数。主模块发送到空闲 cpu 的是创建一个新的 python 进程,几乎只有函数声明传递给它的参数和必要的导入模块,一切都准备好执行,它不能回头问什么。

      对我有用的还有在一个非常切割的函数(不是我创建的)中使用apply_async,它使用进程作为键和来自apply_async 的结果作为值创建字典,然后将其放入try/except 如果一个进程发生错误,它不会停止主进程的执行。 类似的东西:

      def computeplane_multi(processList):
          pool = Pool(processes=4, maxtasksperchild=10)
          jobs= {}
          for item in processList:
              jobs[item[0]] = pool.apply_async(computeplane, [x for x in item])
          for item,result in jobs.items():
              try:
                  result = result.get()
              except Exception as e:
                  print(e)
          pool.close()
          pool.join()
      

      回到你的代码,我对你做了一些修改尝试(并告诉我):

      import arcpy
      import numpy
      import multiprocessing
      import tempfile
      
      bathymetry_path = r'C:/GIS workspace/RRE/habitat.gdb/bathymetry_NGVD_meters'
      zones_path = r'C:/GIS workspace/RRE/habitat.gdb/markerzones_meters'
      table_folder = r'C:/GIS workspace/RRE/zonetables'
      
      def computeplane(bathymetry_path,zones_path,wsedepth):
          def processtable(raster_obj, zones_path, w, z, out_folder, out_file):
              zone_file = arcpy.sa.Raster(zones_path)
              temp_name = "/" + next(tempfile._get_candidate_names()) + ".dbf"
              arcpy.sa.ZonalStatisticsAsTable(zone_file, 'VALUE', raster_obj, out_folder + temp_name, "DATA", "SUM")
              arcpy.AddField_management(out_folder + temp_name, "wse", 'TEXT')
              arcpy.AddField_management(out_folder + temp_name, "z", 'TEXT')
              arcpy.CalculateField_management(out_folder + temp_name, "wse", "'" + str(w) + "'", "PYTHON")
              arcpy.CalculateField_management(out_folder + temp_name, "z", "'" + str(z) + "'", "PYTHON")
              arcpy.TableToTable_conversion(out_folder + temp_name, out_folder, out_file)
              arcpy.Delete_management(out_folder + temp_name)
          bathymetry = arcpy.sa.Raster(bathymetry_path)
          wse = wsedepth[0]
          depth = wsedepth[1]
          total = bathymetry < depth
          littoral = total & ((wse - bathymetry) < 2)
          surface = total & ~(littoral) & ((total + wse - depth) < (total + 2))
          profundal = total & ((total + wse - depth) > (total + 5))
          subsurface = total & ~(profundal | surface | littoral)
          # zonal statistics table names
          total_name = 'total_w' + str(wse) + '_z' + str(depth) + '.csv'
          littoral_name = 'littoral_w' + str(wse) + '_z' + str(depth) + '.csv'
          surface_name = 'surface_w' + str(wse) + '_z' + str(depth) + '.csv'
          subsurface_name = 'subsurface_w' + str(wse) + '_z' + str(depth) + '.csv'
          profundal_name = 'profundal_w' + str(wse) + '_z' + str(depth) + '.csv'
          # compute zonal statistics
          processtable(total, zones_path, wse, depth, table_folder, total_name)
          processtable(littoral, zones_path, wse, depth, table_folder, littoral_name)
          processtable(surface, zones_path, wse, depth, table_folder, surface_name)
          processtable(profundal, zones_path, wse, depth, table_folder, profundal_name)
          processtable(subsurface, zones_path, wse, depth, table_folder, subsurface_name)
          print('point processed : {},{}'.format(wsedepth[0],wsedepth[1]))
      
      
      def computeplane_multi(processList):
          pool = Pool(processes=4, maxtasksperchild=10)
          jobs= {}
          for item in processList:
              jobs[item[0]] = pool.apply_async(computeplane, [x for x in item])
          for item,result in jobs.items():
              try:
                  result = result.get()
              except Exception as e:
                  print(e)
          pool.close()
          pool.join()
      
      
      def main():
          watersurface = numpy.arange(-15.8, 2.7, 0.1)
          # take small subset of the tuples: watersurface[33:34]  
          wsedepths = [(watersurface[x], watersurface[y]) for x in range(watersurface.size)[33:34] for y in range(watersurface[0:x+1].size)]
          processList = []
          for i in wsedepths:
              processList.append((bathymetry_path,zones_path,i))
          computeplane_multi(processList)
      
      if __name__ == '__main__':
          main()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-06-28
        • 1970-01-01
        • 1970-01-01
        • 2017-09-01
        • 2023-03-12
        • 2014-04-18
        • 1970-01-01
        • 2023-04-02
        相关资源
        最近更新 更多