-
Notifications
You must be signed in to change notification settings - Fork 81
PythonRasterFunction
This chapter contains details needed to implement a new raster function in a Python module. Every raster function needs to interact with applications on aspects related to pixel data, mask, metadata, and properties associated with incoming and outgoing rasters—and user-specified parameters that control processing. This chapter also serves as a reference for the semantics of the interaction.
The basic model is quite straightforward:
- ArcGIS ships with built-in raster functions that are capable of being chained together towards defining a complex image processing performed on the fly.
- You want your Python object to participate in that processing pipeline.
- To that end, ArcGIS' own Python Raster Function serves as an adapter—behaving as a regular raster function on one end, and interfacing with our Python implementation on the other.
- Each method you implement helps with a specific aspect of that interaction.
-
name
is a short, readable string identifying the class in a chain of raster function. -
description
is a string containing a long explanation of what the raster function does. -
getParameterInfo()
describes all raster and scalar inputs to the raster function. -
getConfiguration()
helps define attributes that configures how input rasters are read and the output raster constructed. -
updateRasterInfo()
enables you to define the location and dimensions of the output raster. -
selectRasters()
enables you to conditionally define the subset of input rasters that need to be read. -
updatePixels()
is the workhorse of the raster function responsible for delivering processed pixels given all scalar and raster inputs. -
updateKeyMetadata()
enables you to define new metadata attributes or override existing values associated with the output raster dataset. -
isLicensed()
enables you to optionally define criteria that control whether execution proceeds given product version and extension information.
-
In the following sections, we'll let's examine these methods in detail.
Here are first few lines of the Windchill Python module.
import numpy as np
class Windchill():
def __init__(self):
self.name = "Wind Chill Function"
self.description = ("This function computes wind chill on the "
"Fahrenheit scale given wind speed and air temperature.")
NumPy is imported to enable the use of multidimensional arrays. The Windchill class encapsulates the behavior and state of our Python raster function. Unless you plan to house multiple raster functions in this one module, we recommend that your class name match your module name.
The __init__()
method allows
us customize our function object (a specific instance of our Windchill class) as soon as it's created.
Here, we set the name
and description
attributes of the object using the
self variable
that represents a newly created instance.
This method provides information on each input parameter expected by the raster function. This method must be defined for the class to be recognized as a valid raster function.
None
A list of dictionary objects where each dictionary contains attributes that describe a parameter. These are the recognized attributes of a parameter:
-
Required
.String
.This attribute contains a keyword that uniquely identifies this parameter in the set of parameters accepted by the raster function object.
-
Required
.String
.This attribute contains a keyword representing the data type of the value held by this parameter. Allowed values are:
numeric
,string
,raster
,rasters
, andboolean
. -
Required
. Default:None
.This attribute contains the default value associated with this parameter.
-
Optional
.Boolean
. Default:False
.This attribute contains a flag indicating whether this parameter is required or optional.
-
Optional
.String
. Default: Value specified in thename
attribute.This attribute contains text representing a friendly name for this parameter. The value of this attribute is used in Python raster function's property page and other UI components.
-
Optional
.Tuple(string)
.None
.This attribute contains a tuple representing the set of allowed values for this parameter. If specified, the property page shows a drop-down list pre-populated with these values. This attribute is applicable only to parameters where
dataType='string'
. Here's an example. -
Optional
.String
. Default:None
.This attribute contains text that describes the parameter in detail. The text is also displayed as a tooltip in the Python raster function's property page.
Here's an example of the method as implemented in the Windchill raster function.
def getParameterInfo(self):
return [
{
'name': 'temperature',
'dataType': 'raster',
'value': None,
'required': True,
'displayName': "Temperature Raster",
'description': "A single-band raster where pixel values represent ambient air temperature in Fahrenheit."
},
{
'name': 'ws',
'dataType': 'raster',
'value': None,
'required': True,
'displayName': "Wind-speed Raster",
'description': "A single-band raster where pixel values represent wind speed measured in miles per hour."
},
]
This method, if defined, manages how the output raster is constructed.
It also controls aspects of parent dataset on which this raster function is applied.
Decisions in this method may be based on the user-updated values of one or more scalar (non-raster) parameters.
This method is invoked after .getParameterInfo()
but before .updateRasterInfo()
by which time all
rasters will have been loaded.
None
A dictionary describing the configuration. These are the recognized attributes:
-
Optional
.Tuple(int)
. Default:None
.Contains indexes of bands of the input raster that need to be extracted. The first band has index 0. If unspecified, all bands of the input raster are available in
.updatePixels()
-
Optional
.Boolean
. Default:False
.Indicates whether all input rasters are composited as a single multi-band raster. If set to
True
, a raster by the namecomposite
is available in.updateRasterInfo()
and.updatePixels()
. -
Optional
.Integer
. Default:0xFF
.Bitwise-OR'd integer that indicates the set of input raster properties that are inherited by the output raster. If unspecified, all properties are inherited. These are the recognized values:
Value Description 1
Pixel type 2
NoData 4
Dimensions (spatial reference, extent, and cell-size) 8
Resampling type -
Optional
.Integer
. Default:0
.A flag constructed as a bitwise-OR'd integer indicating the set of properties of the parent dataset that needs to be invalidated. If unspecified, no property gets invalidated. These are the recognized values:
Value Description 1
XForm stored by the function raster dataset 2
Statistics stored by the function raster dataset 4
Histogram stored by the function raster dataset 8
The key properties stored by the function raster dataset -
Optional
.Integer
. Default:0
.The number of extra pixels needed on each side of input pixel blocks.
-
Optional
.Integer
. Default:1
.CropSizeFixed is a boolean value parameter (1 or 0) in the emd file, representing whether the size of tile cropped around the feature is fixed or not.
Value Description 0
Variable tile size, crop out the feature using the smallest fitting rectangle. This results in tiles 1
Fixed tile size, crop fixed size tiles centered on the feature. The tile can be bigger or smaller than the feature of varying size, both in x and y. the ImageWidth and ImageHeight in the emd file are still passed and used as a maximum cropped size limit. If the feature is bigger than the defined ImageWidth/ImageHeight, the tiles are cropped the same way as in the "fixed tile size" option using the maximum cropped size limit. -
Optional
.Integer
. Default:1
.BlackenAroundFeature is a boolean value paramater (1 or 0) in the emd file, representing whether blacken the pixels outside the feature in each image tile.
Value Description 0
Not Blacken. 1
Blacken. -
Optional
.Boolean
. Default:False
.Indicates whether NoData mask arrays associated with all input rasters are needed by this function for proper construction of output pixels and mask. Set this attribute to
True
only if you need the mask of the input raster(s) in the.updatePixels()
method—perhaps to help you compute output mask. For improved performance, input masks are not made available if this attribute is unspecified.
The Windchill raster function implements this method like this:
def getConfiguration(self, **scalars):
return {
'inheritProperties': 4 | 8, # inherit all but the pixel type and NoData from the input raster
'invalidateProperties': 2 | 4 | 8, # invalidate statistics & histogram on the parent dataset because we modify pixel values.
'inputMask': False # Don't need input raster mask in .updatePixels(). Simply use the inherited NoData.
}
-
Optional
.Integer
. Default:0
.Integer value that represents which resampling method to apply to the output pixel blocks. The list of available resampling methods and an associated integer values are as follows:
Value Description 0
Nearest Neighbor 1
Bilinear Interpolation 2
Cubic Convolution 3
Majority 4
Bilinear Interpolation Plus 5
Bilinear Gaussian Blur 6
Bilinear Gaussian Blur Plus 7
Average 8
Minimum 9
Maximum 10
Vector Average
This method, if defined, updates information that defines the output raster. It's invoked after .getConfiguration()
and each time the dataset containing the python raster function is initialized.
The keyword argument kwargs
contains all user-specified scalar values and information associated with each input rasters.
Use kwargs['x']
to obtain the user-specified value of the scalar whose name
attribute is x
in the .getParameterInfo()
.
If x
represents a raster, kwargs['x_info']
will be a dictionary containing information associated with the raster.
You can access aspects of a particular raster's information like this: kwargs['<rasterName>_info']['<propertyName>']
where is the value of the name
attribute of the raster parameter
and is an aspect of the raster information.
If represents a parameter of type rasters, dataType='rasters'
, then
kwargs['<rasterName>_info']
is a tuple of raster-info dictionaries.
kwargs['output_info']
is always available and populated with values inherited from the first raster and based on
the attributes returned by .getConfiguration()
.
These are the properties associated with a raster information:
-
Integer
The number of bands in the raster.
-
String
representation of pixel type of the raster. These are the allowed values: {
t1
,t2
,t4
,i1
,i2
,i4
,u1
,u2
,u4
,f4
,f8
} as described here -
ndarray(<bandCount> x <dtype>)
An array of one value per raster band representing NoData.
-
Tuple(2 x Floats)
representing cell-size in the x- and y-direction.
-
Tuple(4 x Floats)
representing XMin, YMin, XMax, YMax values of the native image coordinates.
-
Integer
representing the EPSG code of the native image coordinate system.
-
String
representation of the associated XForm between native image and map coordinate systems.
-
Tuple(4 x Floats)
representing XMin, YMin, XMax, YMax values of the map coordinates.
-
Integer
representing the EPSG code of the raster's map coordinate system.
-
Tuple(ndarray(int32), 3 x ndarray(uint8)
A tuple of four arrays where the first array contains 32-bit integers corresponding to pixel values in the indexed raster. The subsequent three arrays contain unsigned 8-bit integers corresponding to the Red, Green, and Blue components of the mapped color. The sizes of all arrays must match and correspond to the number of colors in the RGB image.
-
Tuple(String, Tuple(Strings)
A tuple of a string representing the path of the attribute table, and another tuple representing field names. Use the information in this tuple with arcpy.da.TableToNumPyArray() to access the values.
-
Integer
The number of level of details in the input raster.
-
Tuple(Floats)
Tuple of x- and y-coordinate of the origin.
-
Optional
.Boolean
. Default:False
.Indicates whether the Python raster function is capable of delivering pixels that have been resampled to match the resolution of the request.
When
resampling
isFalse
, the.updatePixels()
method receives pixel blocks of the input raster at the native resolution of the input (or at the resolution of any image pyramid closest to the request resolution). Each pixel block corresponding to the output raster that's delivered by.updatePixels()
still in native resolution—eventually gets resampled by ArcGIS to match request resolution.When
resampling
isTrue
, the.updatePixels()
method receives resampled pixel blocks of the input raster at request resolution and, therefore, delivers output pixel blocks in request resolution.Note: The resolution of the output raster is made available to Python raster function's
.selectRasters()
and.updatePixels()
methods via thecellSize
property. -
Tuple(<bandCount> x numpy.ndarray)
Tuple where each entry is an array of histogram values of a band.
-
Tuple(<bandCount> x Dictionary)
Tuple of values. Each tuple is a dictionary contains the min(float), max (float) and size(int).
-
Tuple(<bandCount> x Dictionary)
Tuple of statistics values. Each entry in the tuple is a dictionary containing the following attributes of band statistics:
Attribute Data Type Description minimum
Float
Approximate lowest value. maximum
Float
Approximate highest value. mean
Float
Approximate average value. standardDeviation
Float
Approximate measure of spread of values about the mean. skipFactorX
Integer
Number of horizontal pixels between samples when calculating statistics. skipFactorY
Integer
Number of vertical pixels between samples when calculating statistics.
- The tuple in
cellSize
andmaximumCellSize
attributes can be used to construct anarcpy.Point
object. - The tuple in extent, nativeExtent and origin attributes can be used to construct an
arcpy.Extent
object. - The EPSG code in
nativeSpatialReference
andspatialReference
attributes can be used to construct anarcpy.SpatialReference
object.
A dictionary containing output raster info.
This method can update the values of the dictionary in kwargs['output_info']
depending on the kind of operation in .updatePixels()
Again, here's a snippet from the Windchill raster function.
def updateRasterInfo(self, **kwargs):
kwargs['output_info']['bandCount'] = 1 # output is a single band raster
kwargs['output_info']['statistics'] = () # we know nothing about the stats of the outgoing raster.
kwargs['output_info']['histogram'] = () # we know nothing about the histogram of the outgoing raster.
kwargs['output_info']['pixelType'] = 'f4'
return kwargs
If you have a list of rasters (e.g. mosaic dataset) that has different spatial extents as input and you want the output to have union extent of the inputs, you need to add this snippet to .updateRasterInfo()
.
def updateRasterInfo(self, **kwargs):
polygon = None
if 'rasters_info' in kwargs:
rasters_info = kwargs['rasters_info']
nRasters = len(rasters_info)
i = 0
while i < nRasters:
raster_info = rasters_info[i]
i = i+1
e = raster_info['extent']
xMin = e[0]
yMin = e[1]
xMax = e[2]
yMax = e[3]
# Create a polygon geometry
array = arcpy.Array([arcpy.Point(xMin, yMin),
arcpy.Point(xMin, yMax),
arcpy.Point(xMax, yMax),
arcpy.Point(xMax, yMin)])
srs_in = arcpy.SpatialReference(raster_info['spatialReference'])
e = arcpy.Polygon(array, spatial_reference = srs_in)
e = e.projectAs(arcpy.SpatialReference(kwargs['output_info']['spatialReference']))
projected_cords = (e.extent.XMin, e.extent.YMin, e.extent.XMax, e.extent.YMax)
if polygon is not None:
polygon = e | polygon
else:
polygon = e
if polygon is not None:
xMin = polygon.extent.XMin
yMin = polygon.extent.YMin
xMax = polygon.extent.XMax
yMax = polygon.extent.YMax
dx = kwargs['output_info']['cellSize'][0]
dy = kwargs['output_info']['cellSize'][1]
nCols = int((xMax - xMin) / dx + 0.5);
nRows = int((yMax - yMin) / dy + 0.5);
yMin = yMax - (nRows * dy)
xMax = xMin + (nCols * dx)
kwargs['output_info']['extent'] = (xMin, yMin, xMax, yMax)
kwargs['output_info']['nativeExtent'] = (xMin, yMin, xMax, yMax)
This method, if defined, enables the Python raster function to define a subset of input rasters from which pixels are read
before being fed into the updatePixels
method.
Here's an example from the Select By Pixel Size raster function:
def selectRasters(self, tlc, shape, props):
cellSize = props['cellSize']
v = 0.5 * (cellSize[0] + cellSize[1])
if v < self.threshold:
return ('r1',)
else: return ('r2',)
This method, if defined, provides output pixels based on pixel blocks associated with all input rasters. A python raster function that doesn't actively modify output pixel values doesn't need to define this method.
#####
tlc
Tuple(2 x Floats)
representing the coordinates of the top-left corner of the pixel request.
#####
shape
Tuple(ints)
Represents the shape of ndarray that defines the output pixel block. For a single-band pixel block, the tuple contains two ints (rows, columns). For multi-band output raster, the tuple defines a three-dimensional array (bands, rows, columns). The shape associated with the outgoing pixel block and mask must match this argument's value.
#####
props
dictionary
This argument contains properties that define the output raster from which
a pixel block—of dimension and location is defined by the shape
and tlc
arguments—is being requested.
These are the available attributes in this dictionary:
-
extent
:Tuple(4 x Floats)
representing XMin, YMin, XMax, YMax values of the output raster's map coordinates. -
pixelType
:String
representation of pixel type of the raster. These are the allowed values: {t1
,t2
,t4
,i1
,i2
,i4
,u1
,u2
,u4
,f4
,f8
} as described here -
spatialReference
:Integer
representing the EPSG code of the output raster's map coordinate system. -
cellSize
:Tuple(2 x Floats)
representing cell-size in the x- and y-direction. -
width
:Integer
representing number of columns of pixels in the output raster. -
height
:Integer
representing number of rows of pixels in the output raster. -
noData
:Tuple(Numeric)
representing invalid or excluded value per band.
#####
pixelBlocks
dictionary
Keyword argument containing pixels and mask associated with each input raster.
For a raster parameter with dataType='raster'
and name='x'
, pixelBlocks['x_pixels']
and
pixelBlocks['x_mask']
are numpy.ndarrays of pixel and mask values for that input raster.
For a parameter of type rasters where dataType='rasters'
, these are tuples of ndarrays—one entry per raster.
The arrays are three-dimensional for multi-band rasters.
- The Python raster function assumes all arrays are c-contiguous. Non-c-contiguous arrays are not supported at this time.
- The
pixelBlocks
dictionary does not contain any scalars parameters. Those are only available in.getConfiguration()
or.updateRasterInfo()
.
A dictionary with a numpy array containing pixel values in the output_pixels
key and,
optionally, an array representing the mask in the output_mask
key.
The shape of both arrays must match the shape
argument. The data type of the
output_pixels
array should be as indicated in props['pixelType']
, and the data type
of output_mask
should be u1
.
The updatePixels
method from Windchill
raster function looks like this.
def updatePixels(self, tlc, size, props, **pixelBlocks):
ws = np.array(pixelBlocks['ws_pixels'], dtype='f4', copy=False)
t = np.array(pixelBlocks['temperature_pixels'], dtype='f4', copy=False)
ws16 = np.power(ws, 0.16)
outBlock = 35.74 + (0.6215 * t) - (35.75 * ws16) + (0.4275 * t * ws16)
pixelBlocks['output_pixels'] = outBlock.astype(props['pixelType'], copy=False)
return pixelBlocks
This method, if defined, updates dataset-level or band-level key metadata. When a request for a dataset's key metadata is made, this method allows the python raster function to invalidate or overwrite specific requests.
-
Tuple(string)
containing names of the properties being requested. An empty tuple indicates that all properties are being requested.
-
Integer
representing the raster band for which key metadata is being requested. Values are zero-based. And
bandIndex == -1
indicates that the request is for dataset-level key properties. -
dictionary
Keyword argument containing all currently known metadata (or a subset as defined by the
names
tuple).
####Returns The updated keyMetadata dictionary.
A raster simple example of this method appears, again, in the Windchill raster function.
def updateKeyMetadata(self, names, bandIndex, **keyMetadata):
if bandIndex == -1:
keyMetadata['datatype'] = 'Scientific'
keyMetadata['datatype'] = 'Windchill'
elif bandIndex == 0:
keyMetadata['wavelengthmin'] = None # reset inapplicable band-specific key metadata
keyMetadata['wavelengthmax'] = None
keyMetadata['bandname'] = 'Winchill'
return keyMetadata
This method, if defined, indicates whether this python raster function is licensed to execute. It's invoked soon after the function object is constructed. It enables the python raster function to halt execution, given information about the parent product and the context of execution. It also allows the function to, optionally, indicate the expected product level and extension that must be available before execution can proceed.
The productInfo
keyword argument describes the current execution environment. It contains the following attributes:
-
String
This attribute contains the name of the product {
Desktop
,Server
,Engine
, ...} -
String
This attribute contains the version associated with the product.
-
String
This attribute contains the installation path of the product.
-
Integer
This attribute contains the major version number of the product.
-
Float
This attribute contains the minor version number of the product.
-
Integer
This attribute contains the build number associated with the product.
-
Integer
This attribute contains the service pack number, if applicable.
-
Integer
This attribute contains the service pack build, if applicable.
A dictionary containing an attribute that indicates whether licensing checks specific to this python raster function has passed. The dictionary may contain additional attributes that control additional licensing checks enforced by ArcGIS Python Raster Function:
-
Required
.Boolean
.This attribute indicates whether it's OK to proceed with the use of this raster function object. This attribute must be present and specifically set to
False
for execution to halt. Otherwise, it's assumed to beTrue
and that it's OK to proceed. -
Optional
.String
.This attribute contains message to be displayed to the user when
okToRun
isFalse
. -
Optional
.String
.This attribute contains the product license-level expected from the parent application. Allowed values include:
Basic
,Standard
,Advanced
. -
Optional
.String
.This attribute contains the name of the extension that must be available before ArcGIS is allowed to use this raster function. The set of recognized extension names are enumerated here.
def isLicensed(self, **productInfo):
major = productInfo.get('major', 0)
minor = productInfo.get('minor', 0.0)
build = productInfo.get('build', 0)
return {
'okToRun': major >= 10 and minor >= 3.0 and build >= 4276,
'message': "The python raster function is only compatible with ArcGIS 10.3 build 4276",
'productLevel': 'Standard',
'extension': 'Image'
}