Solved! Go to Solution.
So this is what I came up with (see code below). It creates a table with the following structure:

The POL_OID field will be added to the polygons also to allow for a relate. It holds the raster name and the fields for the percentiles.
Change this:
import arcpy
import os
def main():
# settings
ras_ws = r"C:\Forum\ZonalStatsPercLoop\gdb\Rasters.gdb" # input raster workspace
fc = r"C:\Forum\ZonalStatsPercLoop\gdb\PolygonAndOutput.gdb\polygons" # input polygons
tbl = r"C:\Forum\ZonalStatsPercLoop\gdb\PolygonAndOutput.gdb\ZonalStats5" # output related table
lst_perc = [2, 5, 10, 90, 95, 98] # list of percentiles to be calculated
fld_prefix = "Perc_"
fld_poloid = "POL_OID"
fld_rasname = "RasterName"
# create table
tbl_ws, tbl_name = os.path.split(tbl)
arcpy.CreateTable_management(tbl_ws, tbl_name)
# add fields and fill fields list
flds_tbl = [fld_poloid, fld_rasname]
arcpy.AddField_management(tbl, fld_poloid, "LONG")
arcpy.AddField_management(tbl, fld_rasname, "TEXT", 50)
for perc in lst_perc:
fld_perc = "{0}{1}".format(fld_prefix, perc)
arcpy.AddField_management(tbl, fld_perc, "LONG")
flds_tbl.append(fld_perc)
# Enable Spatial analyst
arcpy.CheckOutExtension("Spatial")
# get list of rasters
lst_ras = getListOfRasterFromWS(ras_ws)
# environments
arcpy.env.workspace = "IN_MEMORY"
arcpy.env.overwriteOutput = True
# create dictionary with polygons OID vs lst_parts
dct_pols = createDictPolygons(fc, fld_poloid)
# start insert cursor for table
with arcpy.da.InsertCursor(tbl, flds_tbl) as curs_tbl:
# start loop through rasters
i = 0
for ras_name in lst_ras:
ras = os.path.join(ras_ws, ras_name)
i += 0
print "Processing Raster '{0}'".format(ras_name)
# loop through polygons
for oid, lst_parts in dct_pols.items():
lst_row = [oid, ras_name]
print " - Processing polygon: {0}".format(oid)
# Execute ExtractByPolygon (you can't send the polygon object)
print " - ExtractByPolygon..."
ras_pol = arcpy.sa.ExtractByPolygon(ras, lst_parts, "INSIDE")
outname = "ras{0}pol{1}".format(i, oid)
ras_pol.save(outname)
print " - saved raster as {0}".format(outname)
# create dictionary with value vs count
print " - fill dict with Value x Count"
flds_ras = ("VALUE", "COUNT")
dct = {row[0]:row[1] for row in arcpy.da.SearchCursor(outname, flds_ras)}
# calculate number of pixels in raster
print " - determine sum"
cnt_sum = sum(dct.values())
print " - sum={0}".format(cnt_sum)
# loop through dictionary and create new dictionary with val vs percentile
print " - create percentile dict"
dct_per = {}
cnt_i = 0
for val in sorted(dct.keys()):
cnt_i += dct[val]
dct_per[val] = cnt_i / cnt_sum
# loop through list of percentiles
print " - iterate percentiles"
for perc in lst_perc:
# use dct_per to determine percentiles
perc_dec = float(perc) / 100
print " - Perc_dec for is {0}".format(perc_dec)
pixval = GetPixelValueForPercentile(dct_per, perc_dec)
print " - Perc for {0}% is {1}".format(perc, pixval)
# write pixel value to percentile field
print " - Store value in list"
# fld_perc = "{0}{1}".format(fld_prefix, perc)
# row[flds.index(fld_perc)] = pixval
lst_row.append(pixval)
# update row
print " - insert row"
row = tuple(lst_row)
curs_tbl.insertRow(row)
# return SA license
arcpy.CheckInExtension("Spatial")
print "Ready..."
def createDictPolygons(fc, fld_poloid):
flds = ("OID@", "SHAPE@", fld_poloid)
# add pol oid field for link
if not FieldExist(fc, fld_poloid):
arcpy.AddField_management(fc, fld_poloid, "LONG")
dct_pols = {}
with arcpy.da.UpdateCursor(fc, flds) as curs:
for row in curs:
oid = row[0]
polygon = row[1]
row[2] = oid
lst_parts = []
if polygon.partCount == 1:
for part in polygon:
for pnt in part:
x, y = pnt.X, pnt.Y
lst_parts.append(arcpy.Point(x, y))
else:
for part in polygon:
lst_crds = []
for pnt in part:
x, y = pnt.X, pnt.Y
lst_crds.append(arcpy.Point(x, y))
lst_parts.append(lst_crds)
# add to dict and store pol oid
dct_pols[oid] = lst_parts
curs.updateRow(row)
return dct_pols
def getListOfRasterFromWS(ras_ws):
arcpy.env.workspace = ras_ws
return arcpy.ListRasters()
def GetPixelValueForPercentile(dctper, percentile):
"""will return last pixel value
where percentile LE searched percentile."""
try:
srt_keys = sorted(dctper.keys())
pix_val = srt_keys[0]
for k in srt_keys:
perc = dctper
if perc <= percentile:
pix_val = k
else:
break
return pix_val
except Exception as e:
print " - GetPixelValueForPercentile error: {0}".format(e)
print "dctper:\n{0}".format(dctper)
return -9999
def FieldExist(featureclass, fieldname):
"""Check if field exists"""
import arcpy
fieldList = arcpy.ListFields(featureclass, fieldname)
return len(fieldList) == 1
if __name__ == '__main__':
main()Have fun!
Kind regards, Xander
Works perfectly! Thanks once again Xander.
Glad it works.
If you find it helpful, you may want to mark it as helpful.
Kind regards, Xander
Hi Xander,
I used your code to determine percentiles from a raster but ran into trouble. After encountering a multi-part polygon the code listed the polygon vertices like that
[<Point (3502785.40681, 5526822.79862, #, #)>, etc.]
but failed when extracting polygons:
- ExtractByPolygon...
Traceback (most recent call last):
File "W:\hydrogeology\scripts\percentiles.py", line 87, in <module>
ras_pol = arcpy.sa.ExtractByPolygon(ras, lst_parts, "INSIDE")
File "E:\Programme\ARCGIS\Desktop\Desktop10.1\arcpy\arcpy\sa\Functions.py", line 1231, in ExtractByPolygon
extraction_area)
File "E:\Programme\ARCGIS\Desktop\Desktop10.1\arcpy\arcpy\sa\Utils.py", line 47, in swapper
result = wrapper(*args, **kwargs)
File "E:\Programme\ARCGIS\Desktop\Desktop10.1\arcpy\arcpy\sa\Functions.py", line 1226, in wrapper
extraction_area)
File "E:\Programme\ARCGIS\Desktop\Desktop10.1\arcpy\arcpy\geoprocessing\_base.py", line 498, in <lambda>
return lambda *args: val(*gp_fixargs(args, True))
RuntimeError: Object: Error in executing tool
Do you have any ideas what might cause the trouble? Your help is greatly appreciated!
Best regards,
Axel
Hi Axel Thomas ,
Could you attach a/the polygon that caused the error? That would help me to trace what is going wrong. I could create a multi-polygon over the test area I have, but that might not be the same.
Kind regards, Xander
Xander,
thanks for your fast response. I have attached the complete shape file. You can find the polygon that caused the trouble by looking at the percentile fields at the end of the table and scroll down until the values change to 0.
Hope that helps to fix the error.
Thanks in advance and best regards
Axel
Hi Axel Thomas ,
Thanks for posting the data, I will look into it as soon as I get some things "of my plate"...
Kind regards, Xander
OK, so I loaded your features, used some Spatial Adjustment to move them over a dummy DEM that I have here, and ran the script which reproduced the error that you encountered. I made a small change to the script o assure that no matter is it is multipart it will simply add the points to the list as if it where a single part polygon.... and this works. Checking the result manually generated something very similar. So I think you could do this:
So change this line (around line 55):
if polygon.partCount == 1:
into this:
if polygon.partCount != 0:
... and please try again.

Hi Xander,
replaced the line as you said and the code ran happily away - until polygon 60:
Processing polygon: 60
4300.2_5201
Traceback (most recent call last):
File "W:\hydrogeologie\Verweilzeiten\modellierung_2010\Juelich\skripte\Perzentile_aus_raster.py", line 75, in <module>
x, y = pnt.X, pnt.Y
AttributeError: 'NoneType' object has no attribute 'X'
I guess it means that the raster is smaller than the extent of the polygon layer and the sampled pixels contain NULL values? I checked the polygon in question and as far as I can see there should be no problem - the raster contains '0' values which I would expect to be processed by the code just as any other pixel value. Up until polygon 60 the code has processed quite a number of border polygons without problem. Polygon 60 again is a two-part polygon but with your last change the code processed other polygons of this kind without problem.
Do you have an idea?
.
I will check what is going on with that particular case. What I think might be the case could be due to an island that has a None type point to indicate the island. Will have to see if that is really the case.
Thanks for adding the raster data, that will make things less complex (I had extract just a few polygons until the multipart) so with the raster I can see what else might be going wrong.