arcpy + multiprocessing:error:无法保存栅格数据集

时间:2016-04-25 03:52:59

标签: python python-multiprocessing arcpy

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

基于某些other SO posts,我已将代码包裹在arcpy.Delete_management中,以便main()可能会发挥出色。我使用multiprocessing.Pool创建一组元组,使用main()来进行多重处理。我使用pool.map包来选择dbf文件的名称以避免名称冲突;保证csv文件名不与其他线程冲突。

我已经使用for循环测试了我的代码并且工作正常,但是当我尝试使用tempfile时,我得到了

  

RuntimeError:ERROR 010240:无法将栅格数据集保存到   C:\用户\迈克尔\应用程序数据\本地\ ESRI \ Desktop10.4 \ SpatialAnalyst \ lesst_ras   输出格式为GRID。

这里发生了什么?这个错误没有显示在代码的非多进程版本中,我也没有在任何地方写出栅格 - 然后我再次我不知道pool.map如何处理中间栅格(我肯定不会认为它会将它们留在记忆中 - 尽管它们太大了)。我是否需要告诉arcpy处理多处理的栅格计算的方式?我在下面包含了我的python文件。

arcpy

更新

更多的调查显示,这不是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() 问题,而是ArcGIS进行栅格处理的问题。栅格代数结果被写入默认工作区中的文件;在我的情况下,我没有指定文件夹,因此multiprocessing正在将栅格写入某些AppData文件夹。 ArcGIS根据您的代数表达式使用Lessth,Lessth_1等基本名称。由于我没有指定工作空间,因此所有arcpy个线程都写入此文件夹。虽然单个multiprocessing进程可以跟踪名称,但多个进程都会尝试编写相同的栅格名称并进入另一个进程&#39;锁。

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

2 个答案:

答案 0 :(得分:1)

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

  

致命错误(INFADI)   缺少目录

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

答案 1 :(得分:1)

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

由于您的数据与我以前使用的数据略有不同,因此我无法测试答案,也无法确定其是否可行,但可以说些什么来打开您的胸怀,进行多进程处理,并为您提出一个新的脚本测试。

我已经看到有些事情可以实现,而其他则无法并行化,或者从多进程中几乎得不到任何好处。

使用多进程和arcpy的良好做法是不使用地理数据库,尝试编写实际的'.tif'文件或'in_memory / tempfile',然后在计算后在要并行化的函数内删除。刚开始时不要打扰自己,减少数据以进行测试,在删除临时文件以改善数据质量时,请在其中途添加一些有用的打印内容。

回到问题所在,脚本中有一些无法使用的东西,例如传递arcpy.Raster对象,而不是传递路径到栅格并在函数内部读取的东西。栅格的路径是一个字符串,因此它是“可拾取的”,这意味着它们可以轻松地传递给一个新的过程,因此必须特别注意。

另一件事是声明一个类似于“ table_folder”的变量,并期望您的所有7个核心核都回到第一个核心中的原始进程,询问其路径。 多进程之间不容易共享变量,而与原始代码共享时,您必须使函数独立运行。主模块发送给空闲cpu的东西是创建一个新的python进程,几乎只使用函数声明传递给它的参数和必要的导入模块,所有东西都准备好执行了,它不能回头问些什么了。 / p>

对我有用的也是在一个非常切肉的函数(不是我创建的)中使用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()