def updateRasterInfo(self, **kwargs):
        self.ztMap = None
        self.whereClause = None

        ztStr = kwargs.get('ztable', None)
        ztStr = ztStr.strip() if ztStr else "{}"

        try:
            self.ztMap = loadJSON(ztStr) if ztStr else {}
        except ValueError as e:
            self.ztMap = None

        attribs = kwargs.get('attribs', "")
        attribs = (attribs if attribs else "").split(",")
        self.M = len(attribs)

        if self.ztMap is None:
            self.ztMap = {}
            self.ztTable = ZonalAttributesTable(tableUri=ztStr,
                                                idField=self.zid,
                                                attribList=attribs)

        self.background = kwargs.get('background', None)
        self.background = int(self.background) if self.background else 0
        self.whereClause = kwargs.get('where', None)

        kwargs['output_info']['bandCount'] = 1 + self.M
        kwargs['output_info']['statistics'] = ()
        kwargs['output_info']['histogram'] = ()
        kwargs['output_info']['colormap'] = ()
        return kwargs
    def updateRasterInfo(self, **kwargs):
        self.ztMap = None
        self.whereClause = None

        ztStr = kwargs.get('ztable', None)
        ztStr = ztStr.strip() or "{}"

        try:
            self.ztMap = loadJSON(ztStr) if ztStr else {}
        except ValueError as e:
            self.ztMap = None

        if self.ztMap is None:
            self.ztMap = {}
            self.ztTable = ZonalAttributesTable(tableUri=ztStr,
                                                idField=kwargs.get(
                                                    'zid', None),
                                                attribList=[
                                                    kwargs.get('zmin', None),
                                                    kwargs.get('zmax', None),
                                                    kwargs.get('zval', None)
                                                ])

        self.background = int(kwargs.get('background', None) or 0)
        self.defaultTarget = int(kwargs.get('defzval', None) or 255)
        self.whereClause = kwargs.get('where', None)

        kwargs['output_info']['bandCount'] = 1
        kwargs['output_info']['statistics'] = ()
        kwargs['output_info']['histogram'] = ()
        kwargs['output_info']['colormap'] = ()
        return kwargs
    def updateRasterInfo(self, **kwargs):
        self.ztMap = None
        self.whereClause = None

        ztStr = kwargs.get('ztable', None)
        ztStr = ztStr.strip() if ztStr else "{}"

        try:
            self.ztMap = loadJSON(ztStr) if ztStr else {}
        except ValueError as e:
            self.ztMap = None

        attribs = kwargs.get('attribs', "")
        attribs = (attribs if attribs else "").split(",")
        self.M = len(attribs)

        if self.ztMap is None:
            self.ztMap = {}
            self.ztTable = ZonalAttributesTable(tableUri=ztStr,
                                                idField=self.zid,
                                                attribList=attribs)

        self.background = kwargs.get('background', None)
        self.background = int(self.background) if self.background else 0
        self.whereClause = kwargs.get('where', None)

        kwargs['output_info']['bandCount'] = 1 + self.M
        kwargs['output_info']['statistics'] = () 
        kwargs['output_info']['histogram'] = ()
        kwargs['output_info']['colormap'] = ()
        return kwargs
Exemple #4
0
    def updateRasterInfo(self, **kwargs):
        self.ztMap = None
        self.whereClause = None

        ztStr = kwargs.get('ztable', None)
        ztStr = ztStr.strip() or "{}"

        try:
            self.ztMap = loadJSON(ztStr) if ztStr else {}
        except ValueError as e:
            self.ztMap = None

        if self.ztMap is None:
            self.ztMap = {}
            self.ztTable = ZonalAttributesTable(tableUri=ztStr,
                                                idField=kwargs.get('zid', None),
                                                attribList=[kwargs.get('zmin', None),
                                                            kwargs.get('zmax', None),
                                                            kwargs.get('zval', None)])

        self.background = int(kwargs.get('background', None) or 0)
        self.defaultTarget = int(kwargs.get('defzval', None) or 255)
        self.whereClause = kwargs.get('where', None)

        kwargs['output_info']['bandCount'] = 1
        kwargs['output_info']['statistics'] = ()
        kwargs['output_info']['histogram'] = ()
        kwargs['output_info']['colormap'] = ()
        return kwargs
class RasterizeAttributes():

    def __init__(self):
        self.name = "Rasterize Attributes"
        self.description = ("Enriches a raster through additional bands derived from values of specified attributes "
                            "of an external table or a feature service. You can optionally specify a zone raster "
                            "and the associated zone ID attribute to enable region-based look-up. ")
        self.ztMap = {}                 # zonal thresholds { zoneId:[f1,f2,...,fn], ... }
        self.ztTable = None             # valid only if parameter 'ztable' is not a JSON string (but path or URL)
        self.background = 0
        self.whereClause = None
        self.M = 0                      # number of attribute names == additional bands in the output
        self.zid = None

    def getParameterInfo(self):
        return [
            {
                'name': 'vraster',
                'dataType': 'raster',
                'value': None,
                'required': True,
                'displayName': "Input Raster",
                'description': "The primary input raster."
            },
            {
                'name': 'zraster',
                'dataType': 'raster',
                'value': None,
                'required': False,
                'displayName': "Zone Raster",
                'description': ("An optional single-band zone raster where each pixel contains "
                                "the zone ID associated with the location. The zone ID is used for "
                                "looking up rows in the zonal attributes table for zone-specific ingestion. "
                                "Leave this parameter unspecified to simply import attribute")
            },
            {
                'name': 'ztable',
                'dataType': 'string',
                'value': None,
                'required': False,
                'displayName': "Zonal Attributes Table",
                'description': ("The zonal attributes specified as a JSON string, "
                                "a path to a local feature class or table, or the URL to a feature service layer. "
                                "In JSON, it's described as a collection of mapping from zone IDs to an "
                                "an array of integers, "
                                "like this: { zoneId:[f1,f2,...,fn], ... } ")
            },
            {
                'name': 'zid',
                'dataType': 'string',
                'value': None,
                'required': False,
                'displayName': "Zone ID Field Name",
                'description': ("Name of the field containing the Zone ID values. "
                                "This is only applicable if the 'Zonal Attributes Table' parameter contains path to a table "
                                "or a URL to a feature service.")
            },
            {
                'name': 'attribs',
                'dataType': 'string',
                'value': None,
                'required': False,
                'displayName': "Attribute Field Names",
                'description': ("List of fields in the zonal attributes table separated by a comma. Values in each field will be represented by a band in the output raster.")
            },
            {
                'name': 'background',
                'dataType': 'numeric',
                'value': 0,
                'required': False,
                'displayName': "Background Value",
                'description': ("The initial pixel value of the output raster--before input pixels are remapped.")
            },
            {
                'name': 'where',
                'dataType': 'string',
                'value': None,
                'required': False,
                'displayName': "Where Clause",
                'description': ("Additional query applied on Zonal Attributes table.")
            },
        ]


    def getConfiguration(self, **scalars):
        self.zid = (scalars.get('zid', "") or "").strip()
        self.zid = self.zid if len(self.zid) else None

        return {
          'inheritProperties': 2 | 4 | 8,
          'invalidateProperties': 2 | 4 | 8,        # invalidate statistics & histogram on the parent dataset.
          'inputMask': False                        # Don't need input raster mask in .updatePixels().
        }

    def selectRasters(self, tlc, shape, props):
        return ('vraster', 'zraster') if self.zid else ('vraster',)

    def updateRasterInfo(self, **kwargs):
        self.ztMap = None
        self.whereClause = None

        ztStr = kwargs.get('ztable', None)
        ztStr = ztStr.strip() if ztStr else "{}"

        try:
            self.ztMap = loadJSON(ztStr) if ztStr else {}
        except ValueError as e:
            self.ztMap = None

        attribs = kwargs.get('attribs', "")
        attribs = (attribs if attribs else "").split(",")
        self.M = len(attribs)

        if self.ztMap is None:
            self.ztMap = {}
            self.ztTable = ZonalAttributesTable(tableUri=ztStr,
                                                idField=self.zid,
                                                attribList=attribs)

        self.background = kwargs.get('background', None)
        self.background = int(self.background) if self.background else 0
        self.whereClause = kwargs.get('where', None)

        kwargs['output_info']['bandCount'] = 1 + self.M
        kwargs['output_info']['statistics'] = () 
        kwargs['output_info']['histogram'] = ()
        kwargs['output_info']['colormap'] = ()
        return kwargs


    def updatePixels(self, tlc, shape, props, **pixelBlocks):
        zoneIds = None
        v = pixelBlocks['vraster_pixels'][0]
        z = pixelBlocks.get('zraster_pixels', None) if self.zid else None

        if z is not None:               # zone raster is optional 
            z = z[0]
            zoneIds = np.unique(z)      #TODO: handle no-data and mask in zone raster

        ZT = self.ztTable.query(idList=zoneIds, 
                                where=self.whereClause, 
                                extent=props['extent'], 
                                sr=props['spatialReference']) if self.ztTable else self.ztMap

        # output pixels initialized to background color
        p = np.full(shape=(1 + self.M,) + v.shape,      # band dimension is 1 more than #attributes 
                    fill_value=self.background, 
                    dtype=props['pixelType'])

        np.copyto(p[0], v, casting='unsafe')
        ones = np.ones(v.shape, dtype=bool)
        # use zonal attributes to update output pixels...
        if ZT is not None and len(ZT.keys()):
            for k in (zoneIds if zoneIds is not None else [None]):
                T = ZT.get(k, None)                     # k from z might not be in ztMap
                if not T or not len(T):
                    continue

                I = (z == k) if z is not None else ones
                for b,t in enumerate(T[0], 1):          # first band of p is v, skip it.
                    if t is not None:
                        p[b][I] = t

        pixelBlocks['output_pixels'] = p
        return pixelBlocks
class RasterizeAttributes():
    def __init__(self):
        self.name = "Rasterize Attributes"
        self.description = (
            "Enriches a raster through additional bands derived from values of specified attributes "
            "of an external table or a feature service. You can optionally specify a zone raster "
            "and the associated zone ID attribute to enable region-based look-up. "
        )
        self.ztMap = {}  # zonal thresholds { zoneId:[f1,f2,...,fn], ... }
        self.ztTable = None  # valid only if parameter 'ztable' is not a JSON string (but path or URL)
        self.background = 0
        self.whereClause = None
        self.M = 0  # number of attribute names == additional bands in the output
        self.zid = None

    def getParameterInfo(self):
        return [
            {
                'name': 'vraster',
                'dataType': 'raster',
                'value': None,
                'required': True,
                'displayName': "Input Raster",
                'description': "The primary input raster."
            },
            {
                'name':
                'zraster',
                'dataType':
                'raster',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Zone Raster",
                'description':
                ("An optional single-band zone raster where each pixel contains "
                 "the zone ID associated with the location. The zone ID is used for "
                 "looking up rows in the zonal attributes table for zone-specific ingestion. "
                 "Leave this parameter unspecified to simply import attribute")
            },
            {
                'name':
                'ztable',
                'dataType':
                'string',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Zonal Attributes Table",
                'description':
                ("The zonal attributes specified as a JSON string, "
                 "a path to a local feature class or table, or the URL to a feature service layer. "
                 "In JSON, it's described as a collection of mapping from zone IDs to an "
                 "an array of integers, "
                 "like this: { zoneId:[f1,f2,...,fn], ... } ")
            },
            {
                'name':
                'zid',
                'dataType':
                'string',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Zone ID Field Name",
                'description':
                ("Name of the field containing the Zone ID values. "
                 "This is only applicable if the 'Zonal Attributes Table' parameter contains path to a table "
                 "or a URL to a feature service.")
            },
            {
                'name':
                'attribs',
                'dataType':
                'string',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Attribute Field Names",
                'description':
                ("List of fields in the zonal attributes table separated by a comma. Values in each field will be represented by a band in the output raster."
                 )
            },
            {
                'name':
                'background',
                'dataType':
                'numeric',
                'value':
                0,
                'required':
                False,
                'displayName':
                "Background Value",
                'description':
                ("The initial pixel value of the output raster--before input pixels are remapped."
                 )
            },
            {
                'name':
                'where',
                'dataType':
                'string',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Where Clause",
                'description':
                ("Additional query applied on Zonal Attributes table.")
            },
        ]

    def getConfiguration(self, **scalars):
        self.zid = (scalars.get('zid', "") or "").strip()
        self.zid = self.zid if len(self.zid) else None

        return {
            'inheritProperties': 2 | 4 | 8,
            'invalidateProperties': 2 | 4
            | 8,  # invalidate statistics & histogram on the parent dataset.
            'inputMask':
            False  # Don't need input raster mask in .updatePixels().
        }

    def selectRasters(self, tlc, shape, props):
        return ('vraster', 'zraster') if self.zid else ('vraster', )

    def updateRasterInfo(self, **kwargs):
        self.ztMap = None
        self.whereClause = None

        ztStr = kwargs.get('ztable', None)
        ztStr = ztStr.strip() if ztStr else "{}"

        try:
            self.ztMap = loadJSON(ztStr) if ztStr else {}
        except ValueError as e:
            self.ztMap = None

        attribs = kwargs.get('attribs', "")
        attribs = (attribs if attribs else "").split(",")
        self.M = len(attribs)

        if self.ztMap is None:
            self.ztMap = {}
            self.ztTable = ZonalAttributesTable(tableUri=ztStr,
                                                idField=self.zid,
                                                attribList=attribs)

        self.background = kwargs.get('background', None)
        self.background = int(self.background) if self.background else 0
        self.whereClause = kwargs.get('where', None)

        kwargs['output_info']['bandCount'] = 1 + self.M
        kwargs['output_info']['statistics'] = ()
        kwargs['output_info']['histogram'] = ()
        kwargs['output_info']['colormap'] = ()
        return kwargs

    def updatePixels(self, tlc, shape, props, **pixelBlocks):
        zoneIds = None
        v = pixelBlocks['vraster_pixels'][0]
        z = pixelBlocks.get('zraster_pixels', None) if self.zid else None

        if z is not None:  # zone raster is optional
            z = z[0]
            zoneIds = np.unique(
                z)  #TODO: handle no-data and mask in zone raster

        ZT = self.ztTable.query(
            idList=zoneIds,
            where=self.whereClause,
            extent=props['extent'],
            sr=props['spatialReference']) if self.ztTable else self.ztMap

        # output pixels initialized to background color
        p = np.full(
            shape=(1 + self.M, ) +
            v.shape,  # band dimension is 1 more than #attributes 
            fill_value=self.background,
            dtype=props['pixelType'])

        np.copyto(p[0], v, casting='unsafe')
        ones = np.ones(v.shape, dtype=bool)
        # use zonal attributes to update output pixels...
        if ZT is not None and len(ZT.keys()):
            for k in (zoneIds if zoneIds is not None else [None]):
                T = ZT.get(k, None)  # k from z might not be in ztMap
                if not T or not len(T):
                    continue

                I = (z == k) if z is not None else ones
                for b, t in enumerate(T[0],
                                      1):  # first band of p is v, skip it.
                    if t is not None:
                        p[b][I] = t

        pixelBlocks['output_pixels'] = p
        return pixelBlocks
class ZonalRemap():
    def __init__(self):
        self.name = "Zonal Remap"
        self.description = ("Remap pixels in a raster based on spatial zones "
                            "defined by another raster, and a zone-dependent "
                            "value mapping defined by a table.")
        self.ztMap = {
        }  # zonal thresholds { zoneId:[[zMin,zMax,zVal], ...], ... }
        self.ztTable = None  # valid only if parameter 'ztable' is not a JSON string (but path or URL)
        self.background = 0
        self.defaultTarget = 255
        self.whereClause = None

    def getParameterInfo(self):
        return [
            {
                'name': 'vraster',
                'dataType': 'raster',
                'value': None,
                'required': True,
                'displayName': "Input Raster",
                'description': "The primary single-band input raster."
            },
            {
                'name':
                'zraster',
                'dataType':
                'raster',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Zone Raster",
                'description':
                ("An optional single-band zone raster where each pixel contains "
                 "the zone ID associated with the location. The zone ID is used for "
                 "looking up rows in the zonal threshold table for zone-specific mapping. "
                 "Leave this parameter unspecified to perform zone-independent remapping "
                 "based only on the input pixel value.")
            },
            {
                'name':
                'ztable',
                'dataType':
                'string',
                'value':
                None,
                'required':
                True,
                'displayName':
                "Zonal Thresholds Table",
                'description':
                ("The threshold map specified as a JSON string, "
                 "a path to a local feature class or table, or a URL to a feature service layer. "
                 "In JSON, it's described as a collection of mapping from zone IDs to an "
                 "array of 3-tuples representing the interval (zmin-zmax) and the corresponding output value (zval), "
                 "like this: { zoneId:[[zmin,zmax,zval], ...], ... }.")
            },
            {
                'name':
                'zid',
                'dataType':
                'string',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Zone ID Field Name",
                'description':
                ("Name of the field containing the Zone ID values. "
                 "This is only applicable if the 'Zonal Thresholds' parameter contains path to a table "
                 "or a URL to a feature service.")
            },
            {
                'name':
                'zmin',
                'dataType':
                'string',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Minimum Value Field Name",
                'description':
                ("Name of the field containing the minimum value above which an input pixel gets remapped. "
                 "If left unspecified--or if the field value is null--pixel values are not tested for minimum. "
                 "This is only applicable if the 'Zonal Thresholds Table' parameter contains path to a table "
                 "or a URL to a feature service.")
            },
            {
                'name':
                'zmax',
                'dataType':
                'string',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Maximum Value Field Name",
                'description':
                ("Name of the field containing the maximum value below which an input pixel gets remapped. "
                 "If left unspecified--or if the field value is null--pixel values are not tested for maximum. "
                 "This is only applicable if the 'Zonal Thresholds Table' parameter contains path to a table "
                 "or a URL to a feature service.")
            },
            {
                'name':
                'zval',
                'dataType':
                'string',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Output Value Field Name",
                'description':
                ("Name of the field containing the output value to which an input pixel gets remapped. "
                 "If left unspecified--or if the field value is null--remapped pixel values are set "
                 "to the 'Default Output Value'. "
                 "This is only applicable if the 'Zonal Thresholds Table' parameter contains path to a table "
                 "or a URL to a feature service.")
            },
            {
                'name':
                'background',
                'dataType':
                'numeric',
                'value':
                0,
                'required':
                False,
                'displayName':
                "Background Value",
                'description':
                ("The initial pixel value of the output raster--before input pixels are remapped."
                 )
            },
            {
                'name':
                'defzval',
                'dataType':
                'numeric',
                'value':
                255,
                'required':
                False,
                'displayName':
                "Default Output Value",
                'description':
                ("The default remap/target value of threshold. "
                 "This is the value of the output pixel if either the 'Output Value Field Name' "
                 "parameter is left unspecified or if the output value of the corresponding "
                 "zonal threshold is left unspecified in the Zonal Thresholds Table."
                 )
            },
            {
                'name':
                'where',
                'dataType':
                'string',
                'value':
                None,
                'required':
                False,
                'displayName':
                "Where Clause",
                'description':
                ("Additional query applied on the Zonal Thresholds Table. "
                 "Only the rows that satisfy this criteria participate in the zonal remapping."
                 )
            },
        ]

    def getConfiguration(self, **scalars):
        return {
            'inheritProperties': 2 | 4 | 8,
            'invalidateProperties': 2 | 4
            | 8,  # invalidate statistics & histogram on the parent dataset.
            'inputMask':
            False  # Don't need input raster mask in .updatePixels().
        }

    def updateRasterInfo(self, **kwargs):
        self.ztMap = None
        self.whereClause = None

        ztStr = kwargs.get('ztable', None)
        ztStr = ztStr.strip() or "{}"

        try:
            self.ztMap = loadJSON(ztStr) if ztStr else {}
        except ValueError as e:
            self.ztMap = None

        if self.ztMap is None:
            self.ztMap = {}
            self.ztTable = ZonalAttributesTable(tableUri=ztStr,
                                                idField=kwargs.get(
                                                    'zid', None),
                                                attribList=[
                                                    kwargs.get('zmin', None),
                                                    kwargs.get('zmax', None),
                                                    kwargs.get('zval', None)
                                                ])

        self.background = int(kwargs.get('background', None) or 0)
        self.defaultTarget = int(kwargs.get('defzval', None) or 255)
        self.whereClause = kwargs.get('where', None)

        kwargs['output_info']['bandCount'] = 1
        kwargs['output_info']['statistics'] = ()
        kwargs['output_info']['histogram'] = ()
        kwargs['output_info']['colormap'] = ()
        return kwargs

    def updatePixels(self, tlc, shape, props, **pixelBlocks):
        v = pixelBlocks['vraster_pixels'][0]

        zoneIds = None
        z = pixelBlocks.get('zraster_pixels', None)
        if z is not None:  # zone raster is optional
            z = z[0]
            zoneIds = np.unique(
                z)  #TODO: handle no-data and mask in zone raster

        ZT = self.ztTable.query(
            idList=zoneIds,
            where=self.whereClause,
            extent=props['extent'],
            sr=props['spatialReference']) if self.ztTable else self.ztMap

        # output pixels initialized to background color
        p = np.full(v.shape, self.background, dtype=props['pixelType'])

        # use zonal thresholds to update output pixels...
        if ZT is not None and len(ZT.keys()):
            for k in (zoneIds or [None]):
                T = ZT.get(k, None)  # k from z might not be in ztMap
                if not T:
                    continue

                for t in T:
                    I = (z == k) if z is not None else np.ones(v.shape,
                                                               dtype=bool)
                    if t[0] and t[1]:  # min and max are both available
                        I = I & (v > t[0]) & (v < t[1])
                    elif t[0]:
                        I = I & (v > t[0])
                    elif t[1]:
                        I = I & (v < t[1])
                    p[I] = (t[2] if t[2] is not None else self.defaultTarget)

        pixelBlocks['output_pixels'] = p
        return pixelBlocks
Exemple #8
0
class ZonalRemap():

    def __init__(self):
        self.name = "Zonal Remap"
        self.description = ("Remap pixels in a raster based on spatial zones "
                            "defined by another raster, and a zone-dependent "
                            "value mapping defined by a table.")
        self.ztMap = {}                 # zonal thresholds { zoneId:[[zMin,zMax,zVal], ...], ... }
        self.ztTable = None             # valid only if parameter 'ztable' is not a JSON string (but path or URL)
        self.background = 0
        self.defaultTarget = 255
        self.whereClause = None


    def getParameterInfo(self):
        return [
            {
                'name': 'vraster',
                'dataType': 'raster',
                'value': None,
                'required': True,
                'displayName': "Input Raster",
                'description': "The primary single-band input raster."
            },
            {
                'name': 'zraster',
                'dataType': 'raster',
                'value': None,
                'required': False,
                'displayName': "Zone Raster",
                'description': ("An optional single-band zone raster where each pixel contains "
                                "the zone ID associated with the location. The zone ID is used for "
                                "looking up rows in the zonal threshold table for zone-specific mapping. "
                                "Leave this parameter unspecified to perform zone-independent remapping "
                                "based only on the input pixel value.")
            },
            {
                'name': 'ztable',
                'dataType': 'string',
                'value': None,
                'required': True,
                'displayName': "Zonal Thresholds Table",
                'description': ("The threshold map specified as a JSON string, "
                                "a path to a local feature class or table, or a URL to a feature service layer. "
                                "In JSON, it's described as a collection of mapping from zone IDs to an "
                                "array of 3-tuples representing the interval (zmin-zmax) and the corresponding output value (zval), "
                                "like this: { zoneId:[[zmin,zmax,zval], ...], ... }.")
            },
            {
                'name': 'zid',
                'dataType': 'string',
                'value': None,
                'required': False,
                'displayName': "Zone ID Field Name",
                'description': ("Name of the field containing the Zone ID values. "
                                "This is only applicable if the 'Zonal Thresholds' parameter contains path to a table "
                                "or a URL to a feature service.")
            },
            {
                'name': 'zmin',
                'dataType': 'string',
                'value': None,
                'required': False,
                'displayName': "Minimum Value Field Name",
                'description': ("Name of the field containing the minimum value above which an input pixel gets remapped. "
                                "If left unspecified--or if the field value is null--pixel values are not tested for minimum. "
                                "This is only applicable if the 'Zonal Thresholds Table' parameter contains path to a table "
                                "or a URL to a feature service.")
            },
            {
                'name': 'zmax',
                'dataType': 'string',
                'value': None,
                'required': False,
                'displayName': "Maximum Value Field Name",
                'description': ("Name of the field containing the maximum value below which an input pixel gets remapped. "
                                "If left unspecified--or if the field value is null--pixel values are not tested for maximum. "
                                "This is only applicable if the 'Zonal Thresholds Table' parameter contains path to a table "
                                "or a URL to a feature service.")
            },
            {
                'name': 'zval',
                'dataType': 'string',
                'value': None,
                'required': False,
                'displayName': "Output Value Field Name",
                'description': ("Name of the field containing the output value to which an input pixel gets remapped. "
                                "If left unspecified--or if the field value is null--remapped pixel values are set "
                                "to the 'Default Output Value'. "
                                "This is only applicable if the 'Zonal Thresholds Table' parameter contains path to a table "
                                "or a URL to a feature service.")
            },
            {
                'name': 'background',
                'dataType': 'numeric',
                'value': 0,
                'required': False,
                'displayName': "Background Value",
                'description': ("The initial pixel value of the output raster--before input pixels are remapped.")
            },
            {
                'name': 'defzval',
                'dataType': 'numeric',
                'value': 255,
                'required': False,
                'displayName': "Default Output Value",
                'description': ("The default remap/target value of threshold. "
                                "This is the value of the output pixel if either the 'Output Value Field Name' "
                                "parameter is left unspecified or if the output value of the corresponding "
                                "zonal threshold is left unspecified in the Zonal Thresholds Table.")
            },
            {
                'name': 'where',
                'dataType': 'string',
                'value': None,
                'required': False,
                'displayName': "Where Clause",
                'description': ("Additional query applied on the Zonal Thresholds Table. "
                                "Only the rows that satisfy this criteria participate in the zonal remapping.")
            },
        ]

    def getConfiguration(self, **scalars):
        return {
          'inheritProperties': 2 | 4 | 8,
          'invalidateProperties': 2 | 4 | 8,        # invalidate statistics & histogram on the parent dataset.
          'inputMask': False                        # Don't need input raster mask in .updatePixels().
        }


    def updateRasterInfo(self, **kwargs):
        self.ztMap = None
        self.whereClause = None

        ztStr = kwargs.get('ztable', None)
        ztStr = ztStr.strip() or "{}"

        try:
            self.ztMap = loadJSON(ztStr) if ztStr else {}
        except ValueError as e:
            self.ztMap = None

        if self.ztMap is None:
            self.ztMap = {}
            self.ztTable = ZonalAttributesTable(tableUri=ztStr,
                                                idField=kwargs.get('zid', None),
                                                attribList=[kwargs.get('zmin', None),
                                                            kwargs.get('zmax', None),
                                                            kwargs.get('zval', None)])

        self.background = int(kwargs.get('background', None) or 0)
        self.defaultTarget = int(kwargs.get('defzval', None) or 255)
        self.whereClause = kwargs.get('where', None)

        kwargs['output_info']['bandCount'] = 1
        kwargs['output_info']['statistics'] = ()
        kwargs['output_info']['histogram'] = ()
        kwargs['output_info']['colormap'] = ()
        return kwargs


    def updatePixels(self, tlc, shape, props, **pixelBlocks):
        v = pixelBlocks['vraster_pixels'][0]

        zoneIds = None
        z = pixelBlocks.get('zraster_pixels', None)
        if z is not None:               # zone raster is optional
            z = z[0]
            zoneIds = np.unique(z)      #TODO: handle no-data and mask in zone raster

        ZT = self.ztTable.query(idList=zoneIds,
                                where=self.whereClause,
                                extent=props['extent'],
                                sr=props['spatialReference']) if self.ztTable else self.ztMap

        # output pixels initialized to background color
        p = np.full(v.shape, self.background, dtype=props['pixelType'])

        # use zonal thresholds to update output pixels...
        if ZT is not None and len(ZT.keys()):
            for k in (zoneIds if zoneIds is not None else [None]):
                T = ZT.get(k, None)         # k from z might not be in ztMap
                if not T:
                    continue

                for t in T:
                    I = (z == k) if z is not None else np.ones(v.shape, dtype=bool)
                    if t[0] and t[1]:       # min and max are both available
                        I = I & (v > t[0]) & (v < t[1])
                    elif t[0]:
                        I = I & (v > t[0])
                    elif t[1]:
                        I = I & (v < t[1])
                    p[I] = (t[2] if t[2] is not None else self.defaultTarget)

        pixelBlocks['output_pixels'] = p
        return pixelBlocks