├── .gitignore ├── CONTRIBUTING.md ├── License.txt ├── README.md ├── functions ├── BasicChuckClose.py ├── BasicCubism.py ├── BlockStatistics.py ├── BlockStatistics.rft.xml ├── CompositeBands-4Bands-Ordered.rft.xml ├── CompoundTopographicIndex.py ├── CompoundTopographicIndex_64bitScipy.py ├── Cythonize.py ├── FillRaster.py ├── FillRaster.rft.xml ├── FindMax.py ├── FindMaxPixel_Mosaic.rft.xml ├── FindSecondMax.py ├── FindSecondMaxPixel_Mosaic.rft.xml ├── FindThirdMax.py ├── FindThirdMaxPixel_Mosaic.rft.xml ├── FishHabitatSuitability.py ├── FishHabitatSuitability.rft.xml ├── FuzzyMembership.py ├── GradientBoostedClassifier.py ├── HexagonPixels.py ├── KNearestNeighborsClassifier.py ├── Landsat ETM Pixel Percentile.rft.xml ├── Landsat ETM Scene Synthesis.rft.xml ├── Landsat Image Synthesis.rft.xml ├── Landsat OLI Pixel Percentile.rft.xml ├── Landsat OLI Scene Synthesis.rft.xml ├── Landsat TM Pixel Percentile.rft.xml ├── Landsat TM Scene Synthesis.rft.xml ├── LandsatC2QA.py ├── LandsatImageSynthesis.py ├── LandsatImageSynthesis.rft.xml ├── LandsatMedianImage.py ├── LandsatMedianPixelComposite.py ├── LandsatPixelPercentile.py ├── Landsat_Image_Synthesis.py ├── MaskRaster.py ├── MaskRaster.rft.xml ├── NearestNeighborClassifier_SKLearn.py ├── NearestNeighborsClassifier.py ├── PercentAboveThreshold.py ├── PercentAboveThreshold.rft.xml ├── RandomForestClassifier.py ├── RankFilter.py ├── RankFilter.rft.xml ├── Reference.py ├── RemoveNoData.rft.xml ├── ReplaceNulls.py ├── ReplaceNulls.rft.xml ├── SeasonalARIMA.py ├── SeasonalARIMA.rft.xml ├── SelectByPixelSize.py ├── SelectByPixelSize.rft.xml ├── StepwiseLocalRadiometricAdjustment.py ├── StepwiseLocalRadiometricAdjustment.rft.xml ├── TerrainRuggednessIndex-Riley-Colormap.rft.xml ├── TopographicCCorrection.py ├── TopographicCCorrection.rft.xml ├── VF.rft.xml ├── VineyardAnalysis.py ├── VineyardAnalysis.rft.xml ├── deprecated │ ├── Aggregate.py │ ├── Aggregate.rft.xml │ ├── Arithmetic.py │ ├── AspectSlope.py │ ├── CompositeBands.rft.xml │ ├── ConvertPerSecondToPerMonth.py │ ├── ConvertPerSecondToPerMonth.rft.xml │ ├── DeviationFromMean.rft.xml │ ├── DifferencedNormalizedBurnRatio.py │ ├── FocalStatistics.rft.xml │ ├── HeatIndex.py │ ├── HeatIndex.rft.xml │ ├── Hillshade-ScaleAdjusted-Py.rft.xml │ ├── Hillshade.py │ ├── KeyMetadata.py │ ├── LinearSpectralUnmixing.py │ ├── LinearSpectralUnmixing.rft.xml │ ├── MergeRasters.rft.xml │ ├── MultidirectionalHillshade.rft.xml │ ├── NDVI-Colormap.rft.xml │ ├── NDVI-Grayscale.rft.xml │ ├── NDVI.py │ ├── NDVI.rft.xml │ ├── Normalized-Difference Snow Index (NDSI) for Landsat OLI.rft.xml │ ├── Normalized-Difference Water Index (NDWI) for Landsat OLI.rft.xml │ ├── Random.py │ ├── Random.rft.xml │ ├── RasterizeAttributes.py │ ├── Subtract.rft.xml │ ├── Windchill.py │ ├── Windchill.rft.xml │ ├── ZonalRemap.py │ └── ZonalRemap.rft.xml ├── functions.pyproj └── utils.py ├── raster-functions.sln ├── scripts ├── ExtractRasterInfo.py ├── distribution.txt ├── get-pip.py └── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # Visual Studio files 56 | *.suo 57 | .idea/workspace.xml 58 | .idea/vcs.xml 59 | .idea/modules.xml 60 | .idea/misc.xml 61 | .idea/gbrunner-raster-functions.iml 62 | .idea 63 | pickles/ 64 | functions/Landsat8PixelPercentile.py 65 | .vs/ProjectSettings.json 66 | .vs/slnx.sqlite 67 | .vs/* -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). -------------------------------------------------------------------------------- /functions/BasicChuckClose.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import math 3 | 4 | class BasicChuckClose(): 5 | 6 | def __init__(self): 7 | self.name = "Basic Chuck Close" 8 | self.description = ("This python raster function is the first of which I " 9 | "plan to create that will transform imagery into artwork reminiscent of " 10 | "Chuck Close.") 11 | 12 | def getParameterInfo(self): 13 | return [ 14 | { 15 | 'name': 'dem', 16 | 'dataType': 'raster', 17 | 'value': None, 18 | 'required': True, 19 | 'displayName': "DEM Raster", 20 | 'description': "The digital elevation model (DEM)." 21 | }, 22 | { 23 | 'name': 'inv', 24 | 'dataType': 'boolean', 25 | 'value': True, 26 | 'required': True, 27 | 'displayName': "Invert?", 28 | 'description': "Inverts the image so that higher elevations are darker." 29 | }, 30 | { 31 | 'name': 'show_pix', 32 | 'dataType': 'boolean', 33 | 'value': False, 34 | 'required': True, 35 | 'displayName': "Colorize Pixels?", 36 | 'description': "Gives pixels colors corresponding to their elevation." 37 | } 38 | ] 39 | 40 | def getConfiguration(self, **scalars): 41 | return { 42 | 'compositeRasters': False, 43 | 'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 44 | 'invalidateProperties': 2 | 4 | 8, # reset stats, histogram, key properties 45 | 'inputMask': False 46 | } 47 | 48 | def updateRasterInfo(self, **kwargs): 49 | # repeat stats for all output raster bands 50 | kwargs['output_info']['bandCount'] = 1 51 | kwargs['output_info']['histogram'] = () # reset histogram 52 | kwargs['output_info']['pixelType'] = 'u1' 53 | kwargs['output_info']['noData'] = np.array([0], 'u1') 54 | self.invert = kwargs.get('inv') 55 | self.show_pix = kwargs.get('show_pix') 56 | if not self.show_pix: 57 | kwargs['output_info']['statistics'] = ({'minimum': 0, 'maximum': 1.0}, ) 58 | return kwargs 59 | 60 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 61 | #file = open(r'C:\PROJECTS\gbrunner-raster-functions\test.txt','w') 62 | #file.write(str(z)) 63 | # get the input DEM raster pixel block 64 | inBlock_dem = pixelBlocks['dem_pixels'] 65 | z = inBlock_dem.shape 66 | #file.write(str(z)+'\n') 67 | x, y = z[1],z[2] 68 | chuck_close = np.zeros(z) 69 | square_size = 13 70 | pixel_buffer = 7 71 | maximum = np.max(inBlock_dem) 72 | minimum = np.min(inBlock_dem) 73 | spread = maximum-minimum 74 | break_size = spread/((pixel_buffer-1)) 75 | class_breaks = {} 76 | for i in range(0,int(pixel_buffer)): 77 | class_breaks[i] = minimum+i*break_size 78 | num_squares_x = math.floor(x/square_size) 79 | num_squares_y = math.floor(y/square_size) 80 | #file.write(str(num_squares_x)+'\n') 81 | #file.write(str(num_squares_y)+'\n') 82 | 83 | for num_x in range(1,int(num_squares_x)): 84 | for num_y in range(1,int(num_squares_y)): 85 | 86 | pix = np.mean(inBlock_dem[0,num_x*square_size:(num_x+1)*square_size, num_y*square_size:(num_y+1)*square_size]) 87 | pixel_buffer = get_size(pix, class_breaks) 88 | 89 | ## chuck_close[num_x*square_size-pixel_buffer:num_x*square_size+pixel_buffer, num_y*square_size-pixel_buffer:num_y*square_size+pixel_buffer] = 1 #np.mean(dem[num_x*square_size+pixel_buffer:(num_x+1)*square_size-pixel_buffer, num_y*square_size+pixel_buffer:(num_y+1)*square_size-pixel_buffer])#dem[num_x*square_size, num_y*square_size]#-1*dem[num_x*square_size, num_y*square_size]+maximum 90 | if self.invert: 91 | if self.show_pix: 92 | chuck_close[0,num_x*square_size+pixel_buffer:(num_x+1)*square_size-pixel_buffer, num_y*square_size+pixel_buffer:(num_y+1)*square_size-pixel_buffer]= pix #1 #np.mean(dem[num_x*square_size+pixel_buffer:(num_x+1)*square_size-pixel_buffer, num_y*square_size+pixel_buffer:(num_y+1)*square_size-pixel_buffer])#dem[num_x*square_size, num_y*square_size]#-1*dem[num_x*square_size, num_y*square_size]+maximum 93 | else: 94 | chuck_close[0,num_x*square_size+pixel_buffer:(num_x+1)*square_size-pixel_buffer, num_y*square_size+pixel_buffer:(num_y+1)*square_size-pixel_buffer]= 1 95 | 96 | else: 97 | if self.show_pix: 98 | chuck_close[0,num_x*square_size-pixel_buffer:num_x*square_size+pixel_buffer, num_y*square_size-pixel_buffer:num_y*square_size+pixel_buffer] = pix #1 #np.mean(dem[num_x*square_size+pixel_buffer:(num_x+1)*square_size-pixel_buffer, num_y*square_size+pixel_buffer:(num_y+1)*square_size-pixel_buffer])#dem[num_x*square_size, num_y*square_size]#-1*dem[num_x*square_size, num_y*square_size]+maximum 99 | else: 100 | chuck_close[0,num_x*square_size-pixel_buffer:num_x*square_size+pixel_buffer, num_y*square_size-pixel_buffer:num_y*square_size+pixel_buffer] = 1 101 | 102 | # format output cti pixels 103 | outBlocks = chuck_close.astype(props['pixelType'], copy=False) 104 | #file.write(outBlocks) 105 | #file.close() 106 | pixelBlocks['output_pixels'] = outBlocks 107 | return pixelBlocks 108 | 109 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 110 | if bandIndex == -1: # dataset level 111 | keyMetadata['datatype'] = 'Scientific' 112 | else: # output "band" 113 | keyMetadata['wavelengthmin'] = None 114 | keyMetadata['wavelengthmax'] = None 115 | keyMetadata['bandname'] = 'GIS is Art' 116 | return keyMetadata 117 | 118 | 119 | def get_size(pixel_val, d): 120 | 121 | diff = float('inf') 122 | for key,value in d.items(): 123 | if diff > abs(pixel_val-value): 124 | diff = abs(pixel_val-value) 125 | x = key 126 | 127 | if diff==float('inf'): 128 | x = 1 129 | 130 | return x 131 | -------------------------------------------------------------------------------- /functions/BasicCubism.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import math 3 | 4 | class BasicCubism(): 5 | 6 | def __init__(self): 7 | self.name = "Basic Cubism" 8 | self.description = ("This python raster function sets the foundation for " 9 | "making cubism maps. The takes a block of pixels, subdivides them into " 10 | "larger chunks, and colorizes them by their elevation.") 11 | 12 | def getParameterInfo(self): 13 | return [ 14 | { 15 | 'name': 'dem', 16 | 'dataType': 'raster', 17 | 'value': None, 18 | 'required': True, 19 | 'displayName': "DEM Raster", 20 | 'description': "The digital elevation model (DEM)." 21 | } 22 | ] 23 | 24 | def getConfiguration(self, **scalars): 25 | return { 26 | 'compositeRasters': False, 27 | 'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 28 | 'invalidateProperties': 2 | 4 | 8, # reset stats, histogram, key properties 29 | 'inputMask': False 30 | } 31 | 32 | def updateRasterInfo(self, **kwargs): 33 | # repeat stats for all output raster bands 34 | kwargs['output_info']['bandCount'] = 1 35 | kwargs['output_info']['histogram'] = () # reset histogram 36 | kwargs['output_info']['pixelType'] = 'f4' 37 | kwargs['output_info']['noData'] = np.array([0], 'f4') 38 | return kwargs 39 | 40 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 41 | # get the input DEM raster pixel block 42 | inBlock_dem = pixelBlocks['dem_pixels'] 43 | 44 | z = inBlock_dem.shape 45 | x, y = z[1],z[2] 46 | cubism = np.zeros(z) 47 | 48 | #init_pixel_buffer = 10 change this 49 | pixel_buffer = 1 50 | square_size = 5 51 | maximum = np.max(inBlock_dem) 52 | minimum = np.min(inBlock_dem) 53 | spread = maximum-minimum 54 | break_size = spread/((square_size-1)-1) 55 | class_breaks = {} 56 | for i in range(1,int(square_size)-1): 57 | class_breaks[i] = minimum+i*break_size 58 | num_squares_x = math.floor(x/square_size) 59 | num_squares_y = math.floor(y/square_size) 60 | 61 | for num_x in range(0,int(num_squares_x)): 62 | for num_y in range(0,int(num_squares_y)): 63 | #chuck_close[num_x*square_size:(num_x+1)*square_size, num_y*square_size:(num_y+1)*square_size] = mean #dem[num_x*square_size, num_y*square_size] 64 | cubism[0,num_x*square_size+pixel_buffer:(num_x+1)*square_size-pixel_buffer, num_y*square_size+pixel_buffer:(num_y+1)*square_size-pixel_buffer] = np.mean(inBlock_dem[0,num_x*square_size+pixel_buffer:(num_x+1)*square_size-pixel_buffer, num_y*square_size+pixel_buffer:(num_y+1)*square_size-pixel_buffer])#dem[num_x*square_size, num_y*square_size]#-1*dem[num_x*square_size, num_y*square_size]+maximum 65 | 66 | # format output pixels 67 | outBlocks = cubism.astype(props['pixelType'], copy=False) 68 | pixelBlocks['output_pixels'] = outBlocks 69 | return pixelBlocks 70 | 71 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 72 | if bandIndex == -1: # dataset level 73 | keyMetadata['datatype'] = 'Scientific' 74 | else: # output "band" 75 | keyMetadata['wavelengthmin'] = None 76 | keyMetadata['wavelengthmax'] = None 77 | keyMetadata['bandname'] = 'CTI' 78 | return keyMetadata 79 | -------------------------------------------------------------------------------- /functions/BlockStatistics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from skimage.transform import resize 3 | from skimage.util import view_as_blocks 4 | 5 | 6 | class BlockStatistics(): 7 | 8 | def __init__(self): 9 | self.name = "Block Statistics Function" 10 | self.description = ("Generates a downsampled output raster by computing a statistical " 11 | "measure over non-overlapping square blocks of pixels in the input raster.") 12 | self.func = np.mean 13 | self.padding = 0 14 | 15 | def getParameterInfo(self): 16 | return [ 17 | { 18 | 'name': 'raster', 19 | 'dataType': 'raster', 20 | 'value': None, 21 | 'required': True, 22 | 'displayName': "Input Raster", 23 | 'description': "The primary input raster over which block statistics is computed." 24 | }, 25 | { 26 | 'name': 'size', 27 | 'dataType': 'numeric', 28 | 'value': 1, 29 | 'required': False, 30 | 'displayName': "Block Size", 31 | 'description': ("The number of pixels along each side of the square " 32 | "non-overlapping block.") 33 | }, 34 | { 35 | 'name': 'measure', 36 | 'dataType': 'string', 37 | 'value': 'Mean', 38 | 'required': False, 39 | 'displayName': "Measure", 40 | 'domain': ('Minimum', 'Maximum', 'Mean', 'Median', 'Sum', 'Nearest'), 41 | 'description': ("The statistical measure computed over each " 42 | "block of pixels in the input raster.") 43 | }, 44 | { 45 | 'name': 'factor', 46 | 'dataType': 'numeric', 47 | 'value': 1, 48 | 'required': False, 49 | 'displayName': "Downsampling Factor", 50 | 'description': ("The integer factor by which the output raster is " 51 | "downsampled relative to the input raster.") 52 | }, 53 | ] 54 | 55 | def getConfiguration(self, **scalars): 56 | s = scalars.get('size', None) 57 | s = 3 if s is None else s 58 | self.padding = int(s / 2) 59 | return { 60 | 'samplingFactor': scalars.get('size', 1.0), 61 | 'inheritProperties': 4 | 8, # inherit everything but the pixel type (1) and NoData (2) 62 | 'invalidateProperties': 2 | 4 | 8, # invalidate histogram, statistics, and key metadata 63 | 'inputMask': True, 64 | 'resampling': False, 65 | 'padding': self.padding, 66 | } 67 | 68 | def updateRasterInfo(self, **kwargs): 69 | f = kwargs.get('factor', 1.0) 70 | kwargs['output_info']['cellSize'] = tuple(np.multiply(kwargs['raster_info']['cellSize'], f)) 71 | kwargs['output_info']['pixelType'] = 'f4' # output pixels values are floating-point 72 | kwargs['output_info']['statistics'] = () 73 | kwargs['output_info']['histogram'] = () 74 | 75 | m = kwargs.get('measure') 76 | m = m.lower() if m is not None and len(m) else 'mean' 77 | 78 | if m == 'minimum': 79 | self.func = np.min 80 | elif m == 'maximum': 81 | self.func = np.max 82 | elif m == 'mean': 83 | self.func = np.mean 84 | elif m == 'median': 85 | self.func = np.median 86 | elif m == 'sum': 87 | self.func = np.sum 88 | elif m == 'nearest': 89 | self.func = None 90 | 91 | return kwargs 92 | 93 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 94 | p = pixelBlocks['raster_pixels'] 95 | m = pixelBlocks['raster_mask'] 96 | 97 | if self.func is None: 98 | b = resize(p, shape, order=0, preserve_range=True) 99 | else: 100 | blockSizes = tuple(np.divide(p.shape, shape)) 101 | b = np.ma.masked_array(view_as_blocks(p, blockSizes), 102 | view_as_blocks(~m.astype('b1'), blockSizes)) 103 | for i in range(len(b.shape) // 2): 104 | b = self.func(b, axis=-1) 105 | b = b.data 106 | 107 | d = self.padding 108 | pixelBlocks['output_pixels'] = b.astype(props['pixelType'], copy=False) 109 | pixelBlocks['output_mask'] = resize(m, shape, order=0, preserve_range=True).astype('u1', copy=False) 110 | return pixelBlocks 111 | 112 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 113 | if bandIndex == -1: 114 | keyMetadata['datatype'] = 'Processed' 115 | return keyMetadata 116 | -------------------------------------------------------------------------------- /functions/BlockStatistics.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | BlockStatistics 3 | Compute statistical measure over non-overlapping blocks over input raster. 4 | 5 | Block Statistics Function 6 | Generates a downsampled output raster by computing a statistical measure over non-overlapping square blocks of pixels in the input raster. 7 | UNKNOWN 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | raster 14 | size 15 | measure 16 | factor 17 | 18 | 19 | BlockStatistics.py 20 | BlockStatistics 21 | 22 | Raster 23 | 24 | 25 | true 26 | 27 | 28 | size 29 | 30 | 5 31 | false 32 | 33 | 34 | measure 35 | 36 | Mean 37 | false 38 | 39 | 40 | factor 41 | 42 | 10 43 | false 44 | 45 | 46 | 47 | 48 | 0 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /functions/CompositeBands-4Bands-Ordered.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | CompositeBands-4Bands-Ordered 4 | A raster function template. 5 | 6 | Composite Band Function 7 | Combines rasters to form a multiband raster. 8 | UNKNOWN 9 | 10 | 11 | Rasters_2014515_171526_412 12 | 13 | 14 | 15 | Raster1 16 | 17 | 18 | 19 | RasterAlias1 20 | 21 | true 22 | 23 | 24 | Raster2 25 | 26 | 27 | 28 | RasterAlias2 29 | 30 | true 31 | 32 | 33 | Raster3 34 | 35 | 36 | 37 | RasterAlias3 38 | 39 | true 40 | 41 | 42 | Raster4 43 | 44 | 45 | 46 | RasterAlias4 47 | 48 | true 49 | 50 | 51 | false 52 | 53 | 54 | 2 55 | 56 | 57 | GroupName 58 | Tag 59 | -------------------------------------------------------------------------------- /functions/CompoundTopographicIndex.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy import pi 3 | from math import sqrt 4 | 5 | class CompoundTopographicIndex(): 6 | 7 | def __init__(self): 8 | self.name = "Compound Topographic Index" 9 | self.description = ("Computes the compound topographic index (CTI), also " 10 | "known as the topographic wetness index (TWI). " 11 | "This is calculated from an input slope raster and flow " 12 | "accumulation surface and is meant to be used in " 13 | "a raster function chain.") 14 | 15 | def getParameterInfo(self): 16 | return [ 17 | { 18 | 'name': 'slope', 19 | 'dataType': 'raster', 20 | 'value': None, 21 | 'required': True, 22 | 'displayName': "Slope Raster", 23 | 'description': "A slope raster (in degrees) derived from a digital elevation model (DEM)." 24 | }, 25 | { 26 | 'name': 'flow', 27 | 'dataType': 'raster', 28 | 'value': None, 29 | 'required': True, 30 | 'displayName': "Flow Accumulation Raster", 31 | 'description': "A raster representing flow accumulation." 32 | } 33 | ] 34 | 35 | def getConfiguration(self, **scalars): 36 | return { 37 | 'compositeRasters': False, 38 | 'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 39 | 'invalidateProperties': 2 | 4 | 8, # reset stats, histogram, key properties 40 | 'inputMask': False 41 | } 42 | 43 | def updateRasterInfo(self, **kwargs): 44 | # repeat stats for all output raster bands 45 | kwargs['output_info']['bandCount'] = 1 46 | kwargs['output_info']['statistics'] = ({'minimum': 0, 'maximum': 25.0}, ) 47 | kwargs['output_info']['histogram'] = () # reset histogram 48 | kwargs['output_info']['pixelType'] = 'f4' 49 | self.dem_cellsize = kwargs['slope_info']['cellSize'] 50 | return kwargs 51 | 52 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 53 | # get the input DEM raster pixel block 54 | inBlock_slope = pixelBlocks['slope_pixels'] 55 | inBlock_flow = pixelBlocks['flow_pixels'] 56 | cellSize = self.dem_cellsize 57 | 58 | DX = cellSize[0] 59 | DY = cellSize[1] 60 | slope = calc_slope(inBlock_slope) 61 | cti = calc_cti(slope, inBlock_flow, cellSize[0]) 62 | 63 | # format output cti pixels 64 | outBlocks = cti.astype(props['pixelType'], copy=False) 65 | pixelBlocks['output_pixels'] = outBlocks 66 | return pixelBlocks 67 | 68 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 69 | if bandIndex == -1: # dataset level 70 | keyMetadata['datatype'] = 'Scientific' 71 | else: # output "band" 72 | keyMetadata['wavelengthmin'] = None 73 | keyMetadata['wavelengthmax'] = None 74 | keyMetadata['bandname'] = 'CTI' 75 | return keyMetadata 76 | 77 | # supporting business logic functions 78 | def calc_slope(slope_deg): 79 | slope = slope_deg * pi/180 #np.arctan(slope_deg) 80 | return slope 81 | 82 | def calc_cti(slope, flow_acc, cellsize): 83 | tan_slope = np.tan(slope) 84 | tan_slope[tan_slope==0]=0.0001 85 | cti = np.log(((flow_acc+1)*cellsize)/tan_slope) 86 | return cti 87 | -------------------------------------------------------------------------------- /functions/CompoundTopographicIndex_64bitScipy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from math import sqrt 3 | from scipy import sparse as sp 4 | from scipy.sparse import linalg as splg 5 | 6 | class CompoundTopographicIndex_64bitScipy(): 7 | 8 | def __init__(self): 9 | self.name = "Compound Topographic Index" 10 | self.description = ("Computes the compound topographic index (CTI), also " 11 | "known as the topographic wetness index (TWI).") 12 | 13 | def getParameterInfo(self): 14 | return [ 15 | { 16 | 'name': 'dem', 17 | 'dataType': 'raster', 18 | 'value': None, 19 | 'required': True, 20 | 'displayName': "DEM Raster", 21 | 'description': "The digital elevation model (DEM)." 22 | } 23 | ] 24 | 25 | def getConfiguration(self, **scalars): 26 | return { 27 | 'compositeRasters': False, 28 | 'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 29 | 'invalidateProperties': 2 | 4 | 8, # reset stats, histogram, key properties 30 | 'inputMask': False 31 | } 32 | 33 | def updateRasterInfo(self, **kwargs): 34 | # repeat stats for all output raster bands 35 | kwargs['output_info']['bandCount'] = 1 36 | kwargs['output_info']['statistics'] = ({'minimum': 0, 'maximum': 25.0}, ) 37 | kwargs['output_info']['histogram'] = () # reset histogram 38 | kwargs['output_info']['pixelType'] = 'f4' 39 | self.dem_cellsize = kwargs['dem_info']['cellSize'] 40 | return kwargs 41 | 42 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 43 | # get the input DEM raster pixel block 44 | inBlock_dem = pixelBlocks['dem_pixels'] 45 | cellSize = self.dem_cellsize 46 | 47 | slope = calc_slope(inBlock_dem[0,:,:], cellSize[0]) 48 | DX = cellSize[0] 49 | DY = cellSize[1] 50 | flow_direction = calc_flow_direction_d8(DX, DY, inBlock_dem[0,:,:]) 51 | flow_accumulation = calc_flow_accumulation(flow_direction, inBlock_dem.shape) 52 | cti = calc_cti(slope, flow_accumulation, cellSize[0]) 53 | 54 | # format output cti pixels 55 | outBlocks = cti.astype(props['pixelType'], copy=False) 56 | pixelBlocks['output_pixels'] = outBlocks 57 | return pixelBlocks 58 | 59 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 60 | if bandIndex == -1: # dataset level 61 | keyMetadata['datatype'] = 'Scientific' 62 | else: # output "band" 63 | keyMetadata['wavelengthmin'] = None 64 | keyMetadata['wavelengthmax'] = None 65 | keyMetadata['bandname'] = 'CTI' 66 | return keyMetadata 67 | 68 | 69 | # supporting business logic functions 70 | def calc_slope(dem, cellsize): 71 | #Modified from calculation found here: 72 | #http://geoexamples.blogspot.com/2014/03/shaded-relief-images-using-gdal-python.html 73 | 74 | x, y = np.gradient(dem, cellsize, cellsize) 75 | #slope = np.pi/2.0 - np.arctan(np.sqrt(x*x + y*y)) 76 | slope = np.arctan(np.sqrt(x*x + y*y)) 77 | return slope 78 | 79 | 80 | def calc_flow_direction_d8(DX, DY, dem): 81 | #Backgroud found at http://adh.usace.army.mil/new_webpage/main/main_page.htm 82 | #Algorithm modified from http://adh.usace.army.mil/svn/adh/mfarthin/src/samsi/2013/topo/ 83 | 84 | nr = dem.shape[0] 85 | nc = dem.shape[1] 86 | HYP = sqrt(DX*DX + DY*DY) 87 | 88 | ghost = np.zeros((nr+2, nc+2), 'd') 89 | ghost[1:-1, 1:-1] = dem[:, :] 90 | ghost[0, 1:-1] = dem[0, :] 91 | ghost[-1, 1:-1] = dem[-1, :] 92 | ghost[1:-1, 0] = dem[:, 0] 93 | ghost[1:-1, -1] = dem[:, -1] 94 | 95 | ghost[0, 0] = ghost[1, 1] 96 | ghost[-1, -1] = ghost[-2, -2] 97 | 98 | ghost[0, -1] = ghost[1, -2] 99 | ghost[-1, 0] = ghost[-2, 1] 100 | 101 | neig_incr_col_major = np.array([nr, 102 | nr-1, -1, -nr-1, 103 | -nr, 104 | -nr+1, 1, nr+1]) 105 | 106 | neig_incr = neig_incr_col_major 107 | slopes = np.zeros((8,), 'd') 108 | all_indices = np.arange(nr*nc, dtype='i') 109 | max_indices = np.zeros(nr*nc, 'i') 110 | slope_vals = np.zeros(nr*nc, 'd') 111 | slope_count = np.zeros(nr*nc, 'i') 112 | for i in range(1, nr+1): 113 | for j in range(1, nc+1): 114 | # 115 | slopes[0] = (ghost[i, j]-ghost[i, j+1])/DX 116 | slopes[4] = (ghost[i, j]-ghost[i, j-1])/DX 117 | # 118 | slopes[1] = (ghost[i, j]-ghost[i-1, j+1])/HYP 119 | slopes[2] = (ghost[i, j]-ghost[i-1, j])/DY 120 | slopes[3] = (ghost[i, j]-ghost[i-1, j-1])/HYP 121 | # 122 | slopes[5] = (ghost[i, j]-ghost[i+1, j-1])/HYP 123 | slopes[6] = (ghost[i, j]-ghost[i+1, j])/DY 124 | slopes[7] = (ghost[i, j]-ghost[i+1, j+1])/HYP 125 | 126 | glob_ind = (j-1)*nr + i-1 # local cell col major 127 | loc_max = slopes.argmax() 128 | if slopes[loc_max] > 0: 129 | glob_max = min(max(glob_ind + neig_incr[loc_max], 0), nc*nr-1) 130 | max_indices[glob_ind] = glob_max 131 | slope_vals[glob_ind] = slopes[loc_max] 132 | slope_count[glob_ind] = 1 133 | 134 | M = sp.csr_matrix((slope_count, (all_indices, max_indices)), shape=(nr*nc, nr*nc)) 135 | return M 136 | 137 | 138 | def calc_flow_accumulation(M, dsh): 139 | #Backgroud found at http://adh.usace.army.mil/new_webpage/main/main_page.htm 140 | #Algorithm modified from http://adh.usace.army.mil/svn/adh/mfarthin/src/samsi/2013/topo/ 141 | 142 | nc = M.shape[1] 143 | I = sp.eye(M.shape[0], M.shape[1]) 144 | B = I - M.transpose() 145 | b = np.ones(nc, 'd') 146 | #a = sp.linalg.spsolve.spsolve(B, b) 147 | a = splg.spsolve(B, b) 148 | a = a.reshape(dsh, order='F') 149 | return a 150 | 151 | 152 | def calc_cti(slope, flow_acc, cellsize): 153 | #Based on background infotmation found at 154 | #http://gis4geomorphology.com/topographic-index-model/ 155 | #and 156 | #http://gis.stackexchange.com/questions/43276/can-compound-topographic-index-cti-topographic-wetness-index-twi-produce-n 157 | #and 158 | #https://wikispaces.psu.edu/display/AnthSpace/Compound+Topographic+Index 159 | 160 | tan_slope = np.tan(slope) 161 | tan_slope[tan_slope==0]=0.0001 162 | cti = np.log(((flow_acc+1)*cellsize)/tan_slope) 163 | return cti 164 | -------------------------------------------------------------------------------- /functions/Cythonize.py: -------------------------------------------------------------------------------- 1 | """ 2 | Cythonize.py build_ext --inplace 3 | Cythonize.py clean 4 | 5 | """ 6 | 7 | from distutils.core import setup 8 | from Cython.Build import cythonize 9 | 10 | setup(ext_modules = cythonize("*.py")) 11 | -------------------------------------------------------------------------------- /functions/FillRaster.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class FillRaster(): 5 | 6 | def __init__(self): 7 | self.name = "Fill Raster Function" 8 | self.description = ("") 9 | self.fillValue = 0. 10 | 11 | def getParameterInfo(self): 12 | return [ 13 | { 14 | 'name': 'raster', 15 | 'dataType': 'raster', 16 | 'value': None, 17 | 'required': True, 18 | 'displayName': "Input Raster", 19 | 'description': "" 20 | }, 21 | { 22 | 'name': 'value', 23 | 'dataType': 'numeric', 24 | 'value': 0, 25 | 'required': True, 26 | 'displayName': "Fill Value", 27 | 'description': ("") 28 | }, 29 | ] 30 | 31 | def updateRasterInfo(self, **kwargs): 32 | b = kwargs['raster_info']['bandCount'] 33 | self.fillValue = kwargs.get('value', 0.) 34 | kwargs['output_info']['statistics'] = b * ({'minimum': self.fillValue, 'maximum': self.fillValue}, ) 35 | kwargs['output_info']['histogram'] = () 36 | return kwargs 37 | 38 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 39 | pixelBlocks['output_pixels'] = np.full(shape, self.fillValue, dtype=props['pixelType']) 40 | return pixelBlocks 41 | -------------------------------------------------------------------------------- /functions/FillRaster.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | FillRaster 3 | A raster function template. 4 | 5 | Fill Raster Function 6 | 7 | UNKNOWN 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | raster 14 | value 15 | 16 | 17 | FillRaster.py 18 | FillRaster 19 | 20 | Raster 21 | 22 | 23 | true 24 | 25 | 26 | value 27 | 28 | 0 29 | false 30 | 31 | @Field.ObjectID 32 | 33 | 34 | 35 | 36 | 37 | 0 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /functions/FindMax.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import datetime 3 | 4 | # For Debugging 5 | import os 6 | import sys 7 | #import pickle 8 | 9 | #debug_logs_directory = r'C:\Users\greg6750\PycharmProjects\ArtPRF' 10 | 11 | ''' 12 | This raster function performs nearest neighbors analysis using scikit-learn and then maps the 13 | input values to the resulting neighbor array. 14 | 15 | http://scikit-learn.org/stable/documentation.html 16 | http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.NearestNeighbors.html#sklearn.neighbors.NearestNeighbors 17 | https://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html#numpy.loadtxt 18 | ''' 19 | 20 | class FindMax(): 21 | def __init__(self): 22 | self.name = 'FindIndex' 23 | self.description = 'Finds the index of the best raster.' 24 | 25 | 26 | 27 | def getParameterInfo(self): 28 | return [ 29 | { 30 | 'name': 'rasters', 31 | 'dataType': 'rasters', 32 | 'value': None, 33 | 'required': True, 34 | 'displayName': 'Input Rasters', 35 | 'description': 'Rasters for analysis.' 36 | } 37 | ] 38 | 39 | def getConfiguration(self, **scalars): 40 | return { 41 | #'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 42 | 'invalidateProperties': 2 | 4 | 8, # reset stats, histogram, key properties 43 | 'resamplingType': 0 44 | } 45 | 46 | def updateRasterInfo(self, **kwargs): 47 | 48 | 49 | # Output pixel information 50 | kwargs['output_info']['pixelType'] = 'f4' 51 | kwargs['output_info']['noData'] = 0 52 | kwargs['output_info']['histogram'] = () 53 | kwargs['output_info']['bandCount'] = 1 54 | # repeat stats for all output raster bands 55 | kwargs['output_info']['statistics'] = ({'minimum': 0, 'maximum': 25.0}, ) 56 | 57 | 58 | return kwargs 59 | 60 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 61 | 62 | fname = '{:%Y_%b_%d_%H_%M_%S}_t.txt'.format(datetime.datetime.now()) 63 | #filename = os.path.join(debug_logs_directory, fname) 64 | 65 | # Read pixel blocks 66 | pix_blocks = pixelBlocks['rasters_pixels'] 67 | 68 | # Convert pixel blocks to numpy array 69 | pix_array = np.asarray(pix_blocks) 70 | array3d = np.squeeze(pix_array) 71 | array3d[array3d > 100] = -1 72 | #max_val = np.max(array3d, 0) 73 | maxind = np.max(array3d, axis=0) 74 | allzeros = np.max(array3d != -1, 0) 75 | maxind[allzeros == False] = -1 76 | 77 | #pickle_filename = os.path.join(debug_logs_directory, fname) 78 | #pickle.dump(pix_blocks, open(pickle_filename[:-4]+'pix_blocks.p',"wb")) 79 | 80 | # Write output pixels 81 | pixelBlocks['output_pixels'] = maxind.astype( 82 | props['pixelType'], 83 | copy=False 84 | ) 85 | 86 | return pixelBlocks 87 | 88 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 89 | return keyMetadata -------------------------------------------------------------------------------- /functions/FindMaxPixel_Mosaic.rft.xml: -------------------------------------------------------------------------------- 1 | FindMaxPixel_MosaicTestFindIndexFinds the index of the best raster.UNKNOWNrastersExtentTypeCellsizeTypePythonModuleClassNamerasters__IsRasterArray__falseExtentTypefalseCellsizeTypefalseC:\Users\greg6750\PycharmProjects\ArtPRF\FindMax.pyClassNameFindMaxfalse__tans__(rasters)2TagCategoryMatchVariableMatchVariable1falseUnionDimensionUnionDimension0false -------------------------------------------------------------------------------- /functions/FindSecondMax.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import datetime 3 | 4 | # For Debugging 5 | import os 6 | import sys 7 | #import pickle 8 | 9 | #debug_logs_directory = r'C:\Users\greg6750\PycharmProjects\ArtPRF' 10 | 11 | ''' 12 | This raster function performs nearest neighbors analysis using scikit-learn and then maps the 13 | input values to the resulting neighbor array. 14 | 15 | http://scikit-learn.org/stable/documentation.html 16 | http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.NearestNeighbors.html#sklearn.neighbors.NearestNeighbors 17 | https://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html#numpy.loadtxt 18 | ''' 19 | 20 | class FindSecondMax(): 21 | def __init__(self): 22 | self.name = 'FindIndex' 23 | self.description = 'Finds the index of the best raster.' 24 | 25 | 26 | 27 | def getParameterInfo(self): 28 | return [ 29 | { 30 | 'name': 'rasters', 31 | 'dataType': 'rasters', 32 | 'value': None, 33 | 'required': True, 34 | 'displayName': 'Input Rasters', 35 | 'description': 'Rasters for analysis.' 36 | } 37 | ] 38 | 39 | def getConfiguration(self, **scalars): 40 | return { 41 | #'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 42 | 'invalidateProperties': 2 | 4 | 8, # reset stats, histogram, key properties 43 | 'resamplingType': 0 44 | } 45 | 46 | def updateRasterInfo(self, **kwargs): 47 | 48 | 49 | # Output pixel information 50 | kwargs['output_info']['pixelType'] = 'f4' 51 | kwargs['output_info']['noData'] = 0 52 | kwargs['output_info']['histogram'] = () 53 | kwargs['output_info']['bandCount'] = 1 54 | # repeat stats for all output raster bands 55 | kwargs['output_info']['statistics'] = ({'minimum': 0, 'maximum': 25.0}, ) 56 | 57 | 58 | return kwargs 59 | 60 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 61 | 62 | #fname = '{:%Y_%b_%d_%H_%M_%S}_t.txt'.format(datetime.datetime.now()) 63 | #filename = os.path.join(debug_logs_directory, fname) 64 | 65 | # Read pixel blocks 66 | pix_blocks = pixelBlocks['rasters_pixels'] 67 | 68 | # Convert pixel blocks to numpy array 69 | pix_array = np.asarray(pix_blocks) 70 | array3d = np.squeeze(pix_array) 71 | array3d[array3d > 100] = -1 72 | #max_val = np.max(array3d, 0) 73 | secondmaxind = np.sort(array3d, axis=0)[-2] 74 | # maxind = np.argmax(array3d, axis=0) 75 | allzeros = np.max(array3d != -1, 0) 76 | secondmaxind[allzeros == False] = -1 77 | 78 | #pickle_filename = os.path.join(debug_logs_directory, fname) 79 | #pickle.dump(pix_blocks, open(pickle_filename[:-4]+'pix_blocks.p',"wb")) 80 | 81 | # Write output pixels 82 | pixelBlocks['output_pixels'] = secondmaxind.astype( 83 | props['pixelType'], 84 | copy=False 85 | ) 86 | 87 | return pixelBlocks 88 | 89 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 90 | return keyMetadata -------------------------------------------------------------------------------- /functions/FindSecondMaxPixel_Mosaic.rft.xml: -------------------------------------------------------------------------------- 1 | FindSecondMaxPixel_MosaicasdasdFindIndexFinds the index of the best raster.UNKNOWNrastersExtentTypeCellsizeTypePythonModuleClassNamerasters__IsRasterArray__falseExtentTypefalseCellsizeTypefalseC:\Users\greg6750\PycharmProjects\ArtPRF\FindSecondMax.pyClassNameFindSecondMaxfalse__tans__(rasters)2TagCategoryMatchVariableMatchVariable1falseUnionDimensionUnionDimension0false -------------------------------------------------------------------------------- /functions/FindThirdMax.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import datetime 3 | 4 | # For Debugging 5 | import os 6 | import sys 7 | #import pickle 8 | 9 | #debug_logs_directory = r'C:\Users\greg6750\PycharmProjects\ArtPRF' 10 | 11 | ''' 12 | This raster function performs nearest neighbors analysis using scikit-learn and then maps the 13 | input values to the resulting neighbor array. 14 | 15 | http://scikit-learn.org/stable/documentation.html 16 | http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.NearestNeighbors.html#sklearn.neighbors.NearestNeighbors 17 | https://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html#numpy.loadtxt 18 | ''' 19 | 20 | class FindThirdMax(): 21 | def __init__(self): 22 | self.name = 'FindIndex' 23 | self.description = 'Finds the index of the best raster.' 24 | 25 | 26 | 27 | def getParameterInfo(self): 28 | return [ 29 | { 30 | 'name': 'rasters', 31 | 'dataType': 'rasters', 32 | 'value': None, 33 | 'required': True, 34 | 'displayName': 'Input Rasters', 35 | 'description': 'Rasters for analysis.' 36 | } 37 | ] 38 | 39 | def getConfiguration(self, **scalars): 40 | return { 41 | #'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 42 | 'invalidateProperties': 2 | 4 | 8, # reset stats, histogram, key properties 43 | 'resamplingType': 0 44 | } 45 | 46 | def updateRasterInfo(self, **kwargs): 47 | 48 | 49 | # Output pixel information 50 | kwargs['output_info']['pixelType'] = 'f4' 51 | kwargs['output_info']['noData'] = 0 52 | kwargs['output_info']['histogram'] = () 53 | kwargs['output_info']['bandCount'] = 1 54 | # repeat stats for all output raster bands 55 | kwargs['output_info']['statistics'] = ({'minimum': 0, 'maximum': 25.0}, ) 56 | 57 | 58 | return kwargs 59 | 60 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 61 | 62 | #fname = '{:%Y_%b_%d_%H_%M_%S}_t.txt'.format(datetime.datetime.now()) 63 | #filename = os.path.join(debug_logs_directory, fname) 64 | 65 | # Read pixel blocks 66 | pix_blocks = pixelBlocks['rasters_pixels'] 67 | 68 | # Convert pixel blocks to numpy array 69 | pix_array = np.asarray(pix_blocks) 70 | array3d = np.squeeze(pix_array) 71 | array3d[array3d > 100] = -1 72 | #max_val = np.max(array3d, 0) 73 | thirdmaxind = np.sort(array3d, axis=0)[-3] 74 | # maxind = np.argmax(array3d, axis=0) 75 | allzeros = np.max(array3d != -1, 0) 76 | thirdmaxind[allzeros == False] = -1 77 | 78 | #pickle_filename = os.path.join(debug_logs_directory, fname) 79 | #pickle.dump(pix_blocks, open(pickle_filename[:-4]+'pix_blocks.p',"wb")) 80 | 81 | # Write output pixels 82 | pixelBlocks['output_pixels'] = thirdmaxind.astype( 83 | props['pixelType'], 84 | copy=False 85 | ) 86 | 87 | return pixelBlocks 88 | 89 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 90 | return keyMetadata -------------------------------------------------------------------------------- /functions/FindThirdMaxPixel_Mosaic.rft.xml: -------------------------------------------------------------------------------- 1 | FindThirdMaxPixel_MosaicadasdaFindIndexFinds the index of the best raster.UNKNOWNrastersExtentTypeCellsizeTypePythonModuleClassNamerasters__IsRasterArray__falseExtentTypefalseCellsizeTypefalseC:\Users\greg6750\PycharmProjects\ArtPRF\FindThirdMax.pyClassNameFindThirdMaxfalse__tans__(rasters)2TagCategoryMatchVariableMatchVariable1falseUnionDimensionUnionDimension0false -------------------------------------------------------------------------------- /functions/FishHabitatSuitability.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class FishHabitatSuitability(): 5 | 6 | def __init__(self): 7 | self.name = "Fish Habitat Suitability Function" 8 | self.description = "Computes fish habitat suitability by depth." 9 | self.depth = 0.0 10 | 11 | def getParameterInfo(self): 12 | return [ 13 | { 14 | 'name': 'temperature', 15 | 'dataType': 'raster', 16 | 'value': None, 17 | 'required': True, 18 | 'displayname': "Surface Temperature Raster", 19 | 'description': "A single-band raster where values represent surface temperature in Celsius.", 20 | }, 21 | { 22 | 'name': 'salinity', 23 | 'dataType': 'raster', 24 | 'value': None, 25 | 'required': True, 26 | 'displayname': "Surface Salinty Raster", 27 | 'description': "A single-band raster where values represent surface salinity in PSU.", 28 | }, 29 | { 30 | 'name': 'depth', 31 | 'dataType': 'numeric', 32 | 'value': self.depth, 33 | 'required': True, 34 | 'displayname': "Ocean Depth", 35 | 'description': "A numeric value representing ocean depth in meters.", 36 | }, 37 | ] 38 | 39 | def getConfiguration(self, **scalars): 40 | return { 41 | 'inheritProperties': 2 | 4 | 8, # inherit everything but the pixel type (1) 42 | 'invalidateProperties': 2 | 4 | 8 # invalidate these aspects because we are modifying pixels and key metadata 43 | } 44 | 45 | def updateRasterInfo(self, **kwargs): 46 | kwargs['output_info']['bandCount'] = 1 47 | kwargs['output_info']['pixelType'] = 'f4' 48 | kwargs['output_info']['statistics'] = ({'minimum': 0.0, 'maximum': 1.0}, ) 49 | kwargs['output_info']['histogram'] = () 50 | self.depth = abs(float(kwargs['depth'])) 51 | 52 | # piece-wise linear parameters for depth... 53 | d = self.depth 54 | dMinA = 0 55 | dMinP = 2 56 | dMaxP = 11 57 | dMaxA = 20 58 | 59 | if d < dMinA or d > dMaxA: 60 | d = 0.0 61 | elif d <= dMinP: 62 | d = (d - dMinA) / (dMinP - dMinA) 63 | elif d >= dMaxP: 64 | d = (d - dMaxA) / (dMaxP - dMaxA) 65 | else: 66 | d = 1 67 | 68 | self.depth = d 69 | return kwargs 70 | 71 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 72 | t = np.array(pixelBlocks['temperature_pixels'], dtype='f4', copy=False) 73 | s = np.array(pixelBlocks['salinity_pixels'], dtype='f4', copy=False) 74 | 75 | # piece-wise linear parameters for temperature... 76 | tMinA = 17.99 77 | tMinP = 26.37 78 | tMaxP = 29.15 79 | tMaxA = 33.35 80 | 81 | np.putmask(t, t <= tMinP, (t - tMinA) / (tMinP - tMinA)) 82 | np.putmask(t, t >= tMaxP, (t - tMaxA) / (tMaxP - tMaxA)) 83 | np.putmask(t, (t > tMinP) & (t < tMaxP), 1) 84 | np.putmask(t, t < 0, 0) 85 | 86 | # piece-wise linear parameters for salinity... 87 | sMinA = 28.81 88 | sMinP = 32.27 89 | sMaxP = 35.81 90 | sMaxA = 36.79 91 | 92 | np.putmask(s, s <= sMinP, (s - sMinA) / (sMinP - sMinA)) 93 | np.putmask(s, s >= sMaxP, (s - sMaxA) / (sMaxP - sMaxA)) 94 | np.putmask(s, (s > sMinP) & (s < sMaxP), 1) 95 | np.putmask(s, s < 0, 0) 96 | 97 | # get overall probability by tying all conditions 98 | pixelBlocks['output_pixels'] = np.array(t * s * self.depth).astype(props['pixelType'], copy=False) 99 | return pixelBlocks 100 | 101 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 102 | if bandIndex == -1: 103 | keyMetadata['datatype'] = 'Scientific' 104 | keyMetadata['variable'] = 'FishHabitatSuitability' 105 | return keyMetadata 106 | -------------------------------------------------------------------------------- /functions/FishHabitatSuitability.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Fish Habitat Suitability 4 | A raster function template. 5 | 6 | Python Adapter Function 7 | Adapter function for raster functions written in python. 8 | UNKNOWN 9 | 10 | 11 | 12 | PythonModule 13 | temperature 14 | salinity 15 | depth 16 | 17 | 18 | FishHabitatSuitability.py 19 | 20 | water_temp 21 | A single-band raster where values represent surface temperature in Celsius. 22 | 23 | true 24 | 25 | Raster1 26 | 27 | 28 | 29 | salinity 30 | A single-band raster where values represent surface salinity in PSU. 31 | 32 | true 33 | 34 | Raster2 35 | 36 | 37 | 38 | depth 39 | A numeric value representing ocean depth in meters. 40 | 41 | false 42 | 43 | @Field.StdZ 44 | 45 | 46 | 47 | 48 | 49 | 2 50 | 51 | 52 | GroupName 53 | Tag 54 | 55 | -------------------------------------------------------------------------------- /functions/GradientBoostedClassifier.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from sklearn.ensemble import GradientBoostingClassifier 3 | import numpy as np 4 | 5 | ''' 6 | Gradient Boosting for classification. 7 | 8 | GB builds an additive model in a forward stage-wise fashion; 9 | it allows for the optimization of arbitrary differentiable loss functions. 10 | In each stage n_classes_ regression trees are fit on the negative gradient of the binomial or 11 | multinomial deviance loss function. 12 | Binary classification is a special case where only a single regression tree is induced. 13 | 14 | https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html? 15 | highlight=gradient#sklearn.ensemble.GradientBoostingClassifier 16 | ''' 17 | 18 | # Note: Can not name the class GradientBoostedClassifier because it will conflict 19 | # with the scikit-learn class that is imported. 20 | class BoostedClassifier(): 21 | def __init__(self): 22 | self.name = 'Gradient Boosting Classifier (Boosted Regression Trees)' 23 | self.description = 'Gradient Boosting Classifier from scikit-learn ' \ 24 | 'implemented as a Python Raster Function' 25 | 26 | # inputs as string, but eventually will be numpy arrays 27 | self.df = None 28 | self.datafile = None 29 | self.threshold = 0.5 30 | 31 | 32 | def getParameterInfo(self): 33 | return [ 34 | { 35 | 'name': 'rasters', 36 | 'dataType': 'rasters', 37 | 'value': None, 38 | 'required': True, 39 | 'displayName': 'Input Rasters', 40 | 'description': 'Must be in the same order as the columns in the CSV file' 41 | }, 42 | { 43 | 'name': 'training_data_from_file', 44 | 'dataType': 'string', 45 | 'value': 'C:\\PROJECTS\\ML\\training_data.csv', 46 | 'required': True, 47 | 'displayName': 'Training data CSV filepath', 48 | 'description': 'Full filepath directory to training data CSV. ' 49 | 'Internally this will load from disk and be converted to a pandas dataframe.' 50 | } 51 | ] 52 | 53 | def getConfiguration(self, **scalars): 54 | return { 55 | 'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 56 | 'invalidateProperties': 2 | 4 | 8 # reset stats, histogram, key properties 57 | } 58 | 59 | def updateRasterInfo(self, **kwargs): 60 | 61 | # convert filepath string input param to numpy array 62 | self.datafile = str(kwargs['training_data_from_file']) 63 | #self.threshold = float(kwargs['threshold']) 64 | 65 | kwargs['output_info']['pixelType'] = 'f4' 66 | kwargs['output_info']['histogram'] = () 67 | kwargs['output_info']['statistics'] = () 68 | kwargs['output_info']['bandCount'] = 3 69 | 70 | return kwargs 71 | 72 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 73 | 74 | 75 | self.df = pd.read_csv(self.datafile) 76 | 77 | try: 78 | fields_to_drop = ['OBJECTID', 'LOCATION_X', 'LOCATION_Y'] 79 | self.df.drop(fields_to_drop, axis=1, inplace=True) 80 | except: 81 | pass 82 | 83 | y_val = 'VarToPredict' 84 | x_train = self.df.loc[:, self.df.columns != y_val] 85 | y_train = self.df[y_val] 86 | x_train.fillna(0, inplace=True) 87 | y_train.fillna(0, inplace=True) 88 | 89 | # Initialize RandomForestClassifier 90 | # Recommend trying different values for: 91 | # - n_estimators 92 | # - learning_rate 93 | # - max_depth 94 | # - random_state 95 | regr1 = GradientBoostingClassifier(n_estimators=100, learning_rate=1.0, 96 | max_depth=3, random_state=0) 97 | regr1.fit(x_train, y_train) 98 | 99 | pix_blocks = pixelBlocks['rasters_pixels'] 100 | pix_array = np.asarray(pix_blocks) 101 | pix_array = np.squeeze(pix_array) 102 | 103 | pixels_reshaped = pix_array.reshape(pix_array.shape[0], -1).transpose() 104 | 105 | # Run RandomForestRegressor 106 | pred = regr1.predict(pixels_reshaped) 107 | pred_proba = regr1.predict_proba(pixels_reshaped) 108 | 109 | res = np.hstack([np.expand_dims(pred, 1), pred_proba]) 110 | 111 | #res = pred.reshape((pix_array.shape[1], pix_array.shape[2])) 112 | res_reshape = np.reshape( 113 | res.transpose(), 114 | (3, pix_array.shape[1], pix_array.shape[2]) 115 | ) 116 | 117 | res_reshape[res_reshape <= self.threshold] = 0.0 118 | 119 | #res[res <= self.threshold] = 0 120 | # remember that self.n_neighbors is the desired out band count 121 | #res = np.zeros( 122 | # (pix_array.shape[1], pix_array.shape[2]) 123 | #) 124 | 125 | pixelBlocks['output_pixels'] = res_reshape.astype( 126 | props['pixelType'], 127 | copy=True 128 | ) 129 | 130 | return pixelBlocks 131 | 132 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 133 | return keyMetadata 134 | -------------------------------------------------------------------------------- /functions/HexagonPixels.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import math 3 | 4 | class HexagonPixels(): 5 | 6 | def __init__(self): 7 | self.name = "Hexagon Pixels" 8 | self.description = ("Creates a DEM raster of hexagons.") 9 | 10 | def getParameterInfo(self): 11 | return [ 12 | { 13 | 'name': 'dem', 14 | 'dataType': 'raster', 15 | 'value': None, 16 | 'required': True, 17 | 'displayName': "DEM Raster", 18 | 'description': "The digital elevation model (DEM)." 19 | } 20 | ] 21 | 22 | def getConfiguration(self, **scalars): 23 | return { 24 | 'compositeRasters': False, 25 | 'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 26 | 'invalidateProperties': 2 | 4 | 8, # reset stats, histogram, key properties 27 | 'inputMask': False 28 | } 29 | 30 | def updateRasterInfo(self, **kwargs): 31 | # repeat stats for all output raster bands 32 | kwargs['output_info']['bandCount'] = 1 33 | kwargs['output_info']['histogram'] = () # reset histogram 34 | kwargs['output_info']['pixelType'] = 'u1' 35 | kwargs['output_info']['noData'] = np.array([0], 'u1') 36 | 37 | return kwargs 38 | 39 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 40 | # get the input DEM raster pixel block 41 | inBlock_dem = pixelBlocks['dem_pixels'] 42 | hex_pixels = np.zeros(inBlock_dem.shape) 43 | x, y = inBlock_dem.shape 44 | x_pix_size = 9 #8 45 | y_pix_size = 7 46 | maximum = np.max(inBlock_dem) 47 | minimum = np.min(inBlock_dem) 48 | spread = maximum-minimum 49 | num_squares_x = math.floor(x/x_pix_size) 50 | num_squares_y = math.floor(y/y_pix_size) 51 | 52 | #Loop 1 te generate first hexagons 53 | for num_x in range(1,int(num_squares_x)): 54 | for num_y in range(1,int(num_squares_y)): 55 | 56 | pix = np.mean(inBlock_dem[(num_x-1)*x_pix_size:(num_x-1)*x_pix_size+8, (num_y-1)*y_pix_size:(num_y-1)*y_pix_size+6]) 57 | 58 | hex_pixels[(num_x-1)*x_pix_size+3:(num_x-1)*x_pix_size+6,(num_y-1)*y_pix_size+0] = pix 59 | hex_pixels[(num_x-1)*x_pix_size+2:(num_x-1)*x_pix_size+7,(num_y-1)*y_pix_size+1] = pix 60 | hex_pixels[(num_x-1)*x_pix_size+1:(num_x-1)*x_pix_size+8,(num_y-1)*y_pix_size+2] = pix 61 | hex_pixels[(num_x-1)*x_pix_size+0:(num_x-1)*x_pix_size+9,(num_y-1)*y_pix_size+3] = pix 62 | hex_pixels[(num_x-1)*x_pix_size+1:(num_x-1)*x_pix_size+8,(num_y-1)*y_pix_size+4] = pix 63 | hex_pixels[(num_x-1)*x_pix_size+2:(num_x-1)*x_pix_size+7,(num_y-1)*y_pix_size+5] = pix 64 | hex_pixels[(num_x-1)*x_pix_size+3:(num_x-1)*x_pix_size+6,(num_y-1)*y_pix_size+6] = pix 65 | 66 | #Loop 2 to overlay the hexagons on top of the result of loop 1 67 | for num_x in range(1,int(num_squares_x)): 68 | for num_y in range(1,int(num_squares_y)): 69 | 70 | pix = np.mean(inBlock_dem[(num_x-1)*x_pix_size+4:(num_x-1)*x_pix_size+8+4, (num_y-1)*y_pix_size+3:(num_y-1)*y_pix_size+6+3]) 71 | 72 | hex_pixels[(num_x-1)*x_pix_size+3+4:(num_x-1)*x_pix_size+6+4,(num_y-1)*y_pix_size+0+3] = pix 73 | hex_pixels[(num_x-1)*x_pix_size+2+4:(num_x-1)*x_pix_size+7+4,(num_y-1)*y_pix_size+1+3] = pix 74 | hex_pixels[(num_x-1)*x_pix_size+1+4:(num_x-1)*x_pix_size+8+4,(num_y-1)*y_pix_size+2+3] = pix 75 | hex_pixels[(num_x-1)*x_pix_size+0+4:(num_x-1)*x_pix_size+9+4,(num_y-1)*y_pix_size+3+3] = pix 76 | hex_pixels[(num_x-1)*x_pix_size+1+4:(num_x-1)*x_pix_size+8+4,(num_y-1)*y_pix_size+4+3] = pix 77 | hex_pixels[(num_x-1)*x_pix_size+2+4:(num_x-1)*x_pix_size+7+4,(num_y-1)*y_pix_size+5+3] = pix 78 | hex_pixels[(num_x-1)*x_pix_size+3+4:(num_x-1)*x_pix_size+6+4,(num_y-1)*y_pix_size+6+3] = pix 79 | 80 | # format output pixels 81 | outBlocks = hex_pixels.astype(props['pixelType'], copy=False) 82 | pixelBlocks['output_pixels'] = outBlocks 83 | return pixelBlocks 84 | 85 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 86 | if bandIndex == -1: # dataset level 87 | keyMetadata['datatype'] = 'Scientific' 88 | else: # output "band" 89 | keyMetadata['wavelengthmin'] = None 90 | keyMetadata['wavelengthmax'] = None 91 | keyMetadata['bandname'] = 'GIS is Art' 92 | return keyMetadata 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /functions/KNearestNeighborsClassifier.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | from sklearn.neighbors import KNeighborsClassifier 4 | 5 | 6 | ''' 7 | This raster function performs k-nearest neighbors classification 8 | using scikit-learn and generates a map of the classification of 9 | each pixel. 10 | 11 | http://scikit-learn.org/stable/documentation.html 12 | http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.NearestNeighbors.html#sklearn.neighbors.NearestNeighbors 13 | https://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html#numpy.loadtxt 14 | ''' 15 | 16 | class KNNClassifier(): 17 | def __init__(self): 18 | self.name = 'K-Nearest Neighbor Classifier' 19 | self.description = 'This raster function performs k-nearest neighbors classification' \ 20 | ' using scikit-learn and generates a map of the classification of' \ 21 | ' each pixel.' 22 | 23 | # The number of neighbors to use 24 | self.n_neighbors = None 25 | 26 | # The CSV file containing the training data 27 | # The inputs is a string 28 | self.training_data_from_file = None 29 | 30 | def getParameterInfo(self): 31 | return [ 32 | { 33 | 'name': 'rasters', 34 | 'dataType': 'rasters', 35 | 'value': None, 36 | 'required': True, 37 | 'displayName': 'Input Rasters', 38 | 'description': 'This should include several individual rasters. The rasters must be in the same order as the columns in the CSV file' 39 | }, 40 | { 41 | 'name': 'n_neighbors', 42 | 'dataType': 'numeric', 43 | 'value': 5, 44 | 'required': True, 45 | 'displayName': 'Number of neighbors (integer)', 46 | 'description': 'Number of neighbors to use by default for kneighbors queries (integer).' 47 | }, 48 | { 49 | 'name': 'training_data_from_file', 50 | 'dataType': 'string', 51 | 'value': 'C:\\PROJECTS\\ML\\training_data.csv', 52 | 'required': True, 53 | 'displayName': 'Training data CSV filepath', 54 | 'description': 'Full filepath directory to training data CSV. ' 55 | 'Internally this will load from disk and be converted to a pandas dataframe.' 56 | } 57 | ] 58 | 59 | def getConfiguration(self, **scalars): 60 | return { 61 | 'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 62 | 'invalidateProperties': 2 | 4 | 8 # reset stats, histogram, key properties 63 | } 64 | 65 | def updateRasterInfo(self, **kwargs): 66 | self.n_neighbors = int(kwargs['n_neighbors']) 67 | self.datafile = str(kwargs['training_data_from_file']) 68 | 69 | # Number of output bands: 70 | # There should be one band for each neighbor calculated 71 | #out_band_count = self.n_neighbors 72 | 73 | # Output pixel information 74 | kwargs['output_info']['pixelType'] = 'f4' 75 | kwargs['output_info']['statistics'] = () 76 | kwargs['output_info']['histogram'] = () 77 | kwargs['output_info']['bandCount'] = 1 78 | 79 | return kwargs 80 | 81 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 82 | 83 | # Read the input CSV file into a dataframe 84 | self.df = pd.read_csv(self.datafile)#(datafile) 85 | 86 | # Drop the fields that are not involved in predicting or mapping the values. 87 | # These fields will generally be things like "OBJECTID". 88 | fields_to_drop = ['OBJECTID', 'LOCATION_X', 'LOCATION_Y'] # Fields that aren't used in the analysis 89 | self.df.drop(fields_to_drop, axis=1, inplace=True) 90 | 91 | # Separate dataframe into training environmental variables and observed values. 92 | # The environmental values, x_train, are used to train the model 93 | # We are trying to map\predict the y_train value 94 | y_val = 'VarToPredict' # The value that you probably want to predict 95 | x_train = self.df.loc[:, self.df.columns != y_val] 96 | y_train = self.df[y_val] 97 | 98 | # The model won't work if there is missing or null data. 99 | # Fill null values with 0 (or some other value) 100 | x_train.fillna(0, inplace=True) 101 | y_train.fillna(0, inplace=True) 102 | 103 | # Read pixel blocks 104 | pix_blocks = pixelBlocks['rasters_pixels'] 105 | 106 | # Convert pixel blocks to numpy array 107 | pix_array = np.asarray(pix_blocks) 108 | 109 | # Remove any extra indices 110 | pix_array = np.squeeze(pix_array) 111 | 112 | # Reshape the pixels into a 2D array that is number of pixels x number of predictor variables 113 | pixels_reshaped = pix_array.reshape(pix_array.shape[0], -1).transpose() 114 | 115 | # Classify each point using the k-neighbors classifier 116 | knn = KNeighborsClassifier(n_neighbors=self.n_neighbors) 117 | pred = knn.fit(x_train, y_train).predict(pixels_reshaped) 118 | 119 | # Reshape into a 2D array 120 | res = pred.reshape((pix_array.shape[1], pix_array.shape[2])) 121 | 122 | # Write output pixels 123 | pixelBlocks['output_pixels'] = res.astype( 124 | props['pixelType'], 125 | copy=True 126 | ) 127 | 128 | return pixelBlocks 129 | 130 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 131 | return keyMetadata 132 | -------------------------------------------------------------------------------- /functions/Landsat ETM Pixel Percentile.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Landsat ETM Pixel Percentile 4 | Creates a Landsat scene from the 50th percentile pixel at each location given a collection of images that were taken between the start day and year and the end day and year. 5 | 6 | Landsat Pixel Percentile 7 | This function creates a synthetic Landsat image given a day of year rangeand the percentile of the pixel that we want to calculate. 8 | UNKNOWN 9 | 10 | 11 | 12 | rasters 13 | sensor 14 | percentile 15 | start_day 16 | start_year 17 | end_day 18 | end_year 19 | PythonModule 20 | ClassName 21 | 22 | 23 | 24 | rasters 25 | 26 | 27 | 28 | __IsRasterArray__ 29 | 30 | false 31 | 32 | 33 | sensor 34 | 35 | Landsat ETM 36 | false 37 | 38 | 39 | percentile 40 | 41 | 50 42 | false 43 | 44 | 45 | start_day 46 | 47 | 120 48 | false 49 | 50 | 51 | start_year 52 | 53 | 2005 54 | false 55 | 56 | 57 | end_day 58 | 59 | 240 60 | false 61 | 62 | 63 | end_year 64 | 65 | 2012 66 | false 67 | 68 | C:\PROJECTS\gbrunner-raster-functions\functions\LandsatPixelPercentile.py 69 | 70 | ClassName 71 | 72 | LandsatPixelPercentile 73 | false 74 | 75 | 76 | 77 | 78 | __tans__(rasters,sensor,percentile,start_day,start_year,end_day,end_year) 79 | 80 | 81 | 2 82 | 83 | 84 | Tag 85 | Name 86 | 87 | 88 | -------------------------------------------------------------------------------- /functions/Landsat ETM Scene Synthesis.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Landsat ETM Scene Synthesis 4 | Creates a Landsat ETM scene for a user defined month by taking the mean pixel value at that location for that month. 5 | 6 | Landsat Scene Synthesis 7 | This function takes as input a spatial and temporal mosaic dataset of Landsat images, selects images for user defined month, filters out cloudy pixels from each image in the stack, then averages the values along a spatial element to create a synthetic Landsat image for the user defined month. 8 | UNKNOWN 9 | 10 | 11 | 12 | rasters 13 | sensor 14 | predict_month 15 | PythonModule 16 | ClassName 17 | 18 | 19 | 20 | rasters 21 | 22 | 23 | 24 | __IsRasterArray__ 25 | 26 | false 27 | 28 | 29 | sensor 30 | 31 | Landsat ETM 32 | false 33 | 34 | 35 | predict_month 36 | 37 | Jan 38 | false 39 | 40 | C:\PROJECTS\gbrunner-raster-functions\functions\LandsatImageSynthesis.py 41 | 42 | ClassName 43 | 44 | LandsatImageSynthesis 45 | false 46 | 47 | 48 | 49 | 50 | __tans__(rasters,sensor,predict_month) 51 | 52 | 53 | 2 54 | 55 | 56 | Tag 57 | Name 58 | 59 | -------------------------------------------------------------------------------- /functions/Landsat Image Synthesis.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Landsat Image Synthesis 4 | Landsat Scene Generation Raster Function 5 | 6 | Landsat 5 Scene Synthesis 7 | Landsat 5 Scene Synthesis 8 | UNKNOWN 9 | 10 | 11 | 12 | rasters 13 | predict_month 14 | PythonModule 15 | ClassName 16 | 17 | 18 | 19 | rasters 20 | 21 | 22 | 23 | __IsRasterArray__ 24 | 25 | false 26 | 27 | 28 | predict_month 29 | 30 | Mar 31 | false 32 | 33 | Landsat_Image_Synthesis.py 34 | 35 | ClassName 36 | 37 | Landsat_Image_Synthesis 38 | false 39 | 40 | 41 | 42 | 43 | __tans__(rasters,predict_month) 44 | 45 | 46 | 2 47 | 48 | 49 | Tag 50 | Name 51 | 52 | 53 | -------------------------------------------------------------------------------- /functions/Landsat OLI Pixel Percentile.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Landsat OLI Pixel Percentile 4 | Creates a Landsat OLI image from the 50th percentile pixel at each location for a collection of images taken between the start day and year and the end day and year. 5 | 6 | Landsat Pixel Percentile 7 | This function creates a synthetic Landsat image given a day of year rangeand the percentile of the pixel that we want to calculate. 8 | UNKNOWN 9 | 10 | 11 | 12 | rasters 13 | sensor 14 | percentile 15 | start_day 16 | start_year 17 | end_day 18 | end_year 19 | PythonModule 20 | ClassName 21 | 22 | 23 | 24 | rasters 25 | 26 | 27 | 28 | __IsRasterArray__ 29 | 30 | false 31 | 32 | 33 | sensor 34 | 35 | Landsat OLI 36 | false 37 | 38 | 39 | percentile 40 | 41 | 50 42 | false 43 | 44 | 45 | start_day 46 | 47 | 120 48 | false 49 | 50 | 51 | start_year 52 | 53 | 2014 54 | false 55 | 56 | 57 | end_day 58 | 59 | 240 60 | false 61 | 62 | 63 | end_year 64 | 65 | 2017 66 | false 67 | 68 | C:\PROJECTS\gbrunner-raster-functions\functions\LandsatPixelPercentile.py 69 | 70 | ClassName 71 | 72 | LandsatPixelPercentile 73 | false 74 | 75 | 76 | 77 | 78 | __tans__(rasters,sensor,percentile,start_day,start_year,end_day,end_year) 79 | 80 | 81 | 2 82 | 83 | 84 | Tag 85 | Name 86 | 87 | -------------------------------------------------------------------------------- /functions/Landsat OLI Scene Synthesis.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Landsat OLI Scene Synthesis 4 | Creates a Landsat OLI scene for a user defined month by taking the mean pixel value at that location for that month. 5 | 6 | Landsat Scene Synthesis 7 | This function takes as input a spatial and temporal mosaic dataset of Landsat images, selects images for user defined month, filters out cloudy pixels from each image in the stack, then averages the values along a spatial element to create a synthetic Landsat image for the user defined month. 8 | UNKNOWN 9 | 10 | 11 | 12 | rasters 13 | sensor 14 | predict_month 15 | PythonModule 16 | ClassName 17 | 18 | 19 | 20 | rasters 21 | 22 | 23 | 24 | __IsRasterArray__ 25 | 26 | false 27 | 28 | 29 | sensor 30 | 31 | Landsat OLI 32 | false 33 | 34 | 35 | predict_month 36 | 37 | Jan 38 | false 39 | 40 | C:\PROJECTS\gbrunner-raster-functions\functions\LandsatImageSynthesis.py 41 | 42 | ClassName 43 | 44 | LandsatImageSynthesis 45 | false 46 | 47 | 48 | 49 | 50 | __tans__(rasters,sensor,predict_month) 51 | 52 | 53 | 2 54 | 55 | 56 | Tag 57 | Name 58 | 59 | -------------------------------------------------------------------------------- /functions/Landsat TM Pixel Percentile.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Landsat TM Pixel Percentile 4 | Creates a 50th percentile image from a collection of Landsat TM images taken between the start day and year and the end day and year. 5 | 6 | Landsat Pixel Percentile 7 | This function creates a synthetic Landsat image given a day of year rangeand the percentile of the pixel that we want to calculate. 8 | UNKNOWN 9 | 10 | 11 | 12 | rasters 13 | sensor 14 | percentile 15 | start_day 16 | start_year 17 | end_day 18 | end_year 19 | PythonModule 20 | ClassName 21 | 22 | 23 | 24 | rasters 25 | 26 | 27 | 28 | __IsRasterArray__ 29 | 30 | false 31 | 32 | 33 | sensor 34 | 35 | Landsat TM 36 | false 37 | 38 | 39 | percentile 40 | 41 | 50 42 | false 43 | 44 | 45 | start_day 46 | 47 | 120 48 | false 49 | 50 | 51 | start_year 52 | 53 | 1985 54 | false 55 | 56 | 57 | end_day 58 | 59 | 240 60 | false 61 | 62 | 63 | end_year 64 | 65 | 2010 66 | false 67 | 68 | C:\PROJECTS\gbrunner-raster-functions\functions\LandsatPixelPercentile.py 69 | 70 | ClassName 71 | 72 | LandsatPixelPercentile 73 | false 74 | 75 | 76 | 77 | 78 | __tans__(rasters,sensor,percentile,start_day,start_year,end_day,end_year) 79 | 80 | 81 | 2 82 | 83 | 84 | Tag 85 | Name 86 | 87 | -------------------------------------------------------------------------------- /functions/Landsat TM Scene Synthesis.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Landsat TM Scene Synthesis 4 | Creates a Landsat TM scene for a user defined month by taking the mean pixel value at that location for that month. 5 | 6 | Landsat Scene Synthesis 7 | This function takes as input a spatial and temporal mosaic dataset of Landsat images, selects images for user defined month, filters out cloudy pixels from each image in the stack, then averages the values along a spatial element to create a synthetic Landsat image for the user defined month. 8 | UNKNOWN 9 | 10 | 11 | 12 | rasters 13 | sensor 14 | predict_month 15 | PythonModule 16 | ClassName 17 | 18 | 19 | 20 | rasters 21 | 22 | 23 | 24 | __IsRasterArray__ 25 | 26 | false 27 | 28 | 29 | sensor 30 | 31 | Landsat TM 32 | false 33 | 34 | 35 | predict_month 36 | 37 | Jan 38 | false 39 | 40 | C:\PROJECTS\gbrunner-raster-functions\functions\LandsatImageSynthesis.py 41 | 42 | ClassName 43 | 44 | LandsatImageSynthesis 45 | false 46 | 47 | 48 | 49 | 50 | __tans__(rasters,sensor,predict_month) 51 | 52 | 53 | 2 54 | 55 | 56 | Tag 57 | Name 58 | 59 | -------------------------------------------------------------------------------- /functions/LandsatC2QA.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class LandsatC2QA(): 4 | 5 | def __init__(self): 6 | self.name = "Landsat Collection 2 QA Mask" 7 | self.description = "This function creates masks based on Landsat Collection 2 QA band. QA bit index is taken from https://docs.digitalearthafrica.org/en/latest/data_specs/Landsat_C2_SR_specs.html#Quality-assessment-bands" 8 | self.bit_index = {'fill': 0, 'diluted': 1, 'cirrus': 2, 'cloud': 3, 'shadow': 4, 'snow': 5, 'clear': 6, 'water': 7} 9 | 10 | def getParameterInfo(self): 11 | return [ 12 | { 13 | 'name': 'r', 14 | 'dataType': 'raster', 15 | 'value': None, 16 | 'required': True, 17 | 'displayName': "Input Landsat QA band", 18 | 'description': "The input QA raster." 19 | }, 20 | { 21 | 'name': 'fill', 22 | 'dataType': 'boolean', 23 | 'value': False, 24 | 'required': False, 25 | 'displayName': "Mask fill data", 26 | 'description': "Set fill data pixels to 1" 27 | }, 28 | { 29 | 'name': 'diluted', 30 | 'dataType': 'boolean', 31 | 'value': False, 32 | 'required': False, 33 | 'displayName': "Mask dilated cloud", 34 | 'description': "Set dilated cloud pixels to 1" 35 | }, 36 | { 37 | 'name': 'cirrus', 38 | 'dataType': 'boolean', 39 | 'value': False, 40 | 'required': False, 41 | 'displayName': "Mask cirrus cloud", 42 | 'description': "Set cirrus cloud pixels to 1" 43 | }, 44 | { 45 | 'name': 'cloud', 46 | 'dataType': 'boolean', 47 | 'value': False, 48 | 'required': False, 49 | 'displayName': "Mask cloud", 50 | 'description': "Set cloud pixels to 1" 51 | }, 52 | { 53 | 'name': 'shadow', 54 | 'dataType': 'boolean', 55 | 'value': False, 56 | 'required': False, 57 | 'displayName': "Mask cloud shadow", 58 | 'description': "Set cloud shadow pixels to 1" 59 | }, 60 | { 61 | 'name': 'snow', 62 | 'dataType': 'boolean', 63 | 'value': False, 64 | 'required': False, 65 | 'displayName': "Mask snow", 66 | 'description': "Set snow pixels to 1" 67 | }, 68 | { 69 | 'name': 'clear', 70 | 'dataType': 'boolean', 71 | 'value': False, 72 | 'required': False, 73 | 'displayName': "Mask clear", 74 | 'description': "Set clear pixels to 1" 75 | }, 76 | { 77 | 'name': 'water', 78 | 'dataType': 'boolean', 79 | 'value': False, 80 | 'required': False, 81 | 'displayName': "Mask water", 82 | 'description': "Set water pixels to 1" 83 | }, 84 | ] 85 | 86 | def getConfiguration(self, **scalars): 87 | return { 88 | 'compositeRasters': False, 89 | 'inheritProperties': 2 | 4 | 8, # inherit all from the raster but raster type 90 | 'invalidateProperties': 2 | 4 | 8, # reset stats, histogram, key properties 91 | 'inputMask': False 92 | } 93 | 94 | def updateRasterInfo(self, **kwargs): 95 | kwargs['output_info']['bandCount'] = 1 96 | kwargs['output_info']['histogram'] = () # reset histogram 97 | kwargs['output_info']['pixelType'] = 'u1' 98 | kwargs['output_info']['statistics'] = ({'minimum': 0, 'maximum': 1.0}, ) 99 | 100 | fill = int(kwargs.get('fill')) 101 | diluted = int(kwargs.get('diluted')) 102 | cirrus = int(kwargs.get('cirrus')) 103 | cloud = int(kwargs.get('cloud')) 104 | shadow = int(kwargs.get('shadow')) 105 | snow = int(kwargs.get('snow')) 106 | clear = int(kwargs.get('clear')) 107 | water = int(kwargs.get('water')) 108 | 109 | self.bit_mask = (fill << self.bit_index['fill']) + (diluted << self.bit_index['diluted']) + (cirrus << self.bit_index['cirrus']) + (cloud << self.bit_index['cloud']) + (shadow << self.bit_index['shadow']) + (snow << self.bit_index['snow']) + (clear << self.bit_index['clear']) + (water << self.bit_index['water']) 110 | 111 | return kwargs 112 | 113 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 114 | pix_blocks = pixelBlocks['r_pixels'] 115 | pix_array = np.asarray(pix_blocks) 116 | z_dim, x_dim, y_dim = pix_array.shape 117 | 118 | out_mask = np.zeros(pix_array.shape) 119 | 120 | for num_x in range(x_dim): 121 | for num_y in range(y_dim): 122 | if pix_array[0, num_x, num_y] & self.bit_mask: 123 | out_mask[0, num_x, num_y] = 1 # set pixels that have a flag set to 1, otherwise 0 124 | 125 | pixelBlocks['output_pixels'] = out_mask.astype(props['pixelType'], copy=False) 126 | 127 | return pixelBlocks 128 | -------------------------------------------------------------------------------- /functions/LandsatImageSynthesis.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Landsat Image Synthesis 4 | Landsat Scene Generation Raster Function 5 | 6 | Landsat 5 Scene Synthesis 7 | Landsat 5 Scene Synthesis 8 | UNKNOWN 9 | 10 | 11 | 12 | rasters 13 | predict_month 14 | PythonModule 15 | ClassName 16 | 17 | 18 | 19 | rasters 20 | 21 | 22 | 23 | __IsRasterArray__ 24 | 25 | false 26 | 27 | 28 | predict_month 29 | 30 | Mar 31 | false 32 | 33 | Landsat_Image_Synthesis.py 34 | 35 | ClassName 36 | 37 | Landsat_Image_Synthesis 38 | false 39 | 40 | 41 | 42 | 43 | __tans__(rasters,predict_month) 44 | 45 | 46 | 2 47 | 48 | 49 | Tag 50 | Name 51 | 52 | 53 | -------------------------------------------------------------------------------- /functions/MaskRaster.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class MaskRaster(): 4 | 5 | def __init__(self): 6 | self.name = "Mask Raster Function" 7 | self.description = "Applies a raster as the NoData mask of the input raster." 8 | 9 | def getParameterInfo(self): 10 | return [ 11 | { 12 | 'name': 'r', 13 | 'dataType': 'raster', 14 | 'value': None, 15 | 'required': True, 16 | 'displayName': "Input Raster", 17 | 'description': "The primary input raster." 18 | }, 19 | { 20 | 'name': 'm', 21 | 'dataType': 'raster', 22 | 'value': None, 23 | 'required': True, 24 | 'displayName': "Mask Raster", 25 | 'description': "The input mask raster." 26 | }, 27 | ] 28 | 29 | def getConfiguration(self, **scalars): 30 | return { 31 | 'inputMask': True 32 | } 33 | 34 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 35 | M = np.zeros(shape, 'u1') 36 | I = (pixelBlocks['m_pixels'] > 0) & (pixelBlocks['m_mask'] > 0) 37 | np.putmask(M, I, 1) 38 | pixelBlocks['output_mask'] = M 39 | pixelBlocks['output_pixels'] = pixelBlocks['r_pixels'].astype(props['pixelType'], copy=False) 40 | return pixelBlocks 41 | -------------------------------------------------------------------------------- /functions/MaskRaster.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | MaskRaster 3 | Apply a raster as the NoData mask of an input raster. 4 | 5 | Mask Raster Function 6 | Apply a raster as the NoData mask of an input raster. 7 | UNKNOWN 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | r 14 | m 15 | 16 | 17 | MaskRaster.py 18 | MaskRaster 19 | 20 | Raster1 21 | 22 | 23 | true 24 | 25 | 26 | Raster2 27 | 28 | 29 | true 30 | 31 | 32 | 33 | 34 | 2 35 | 36 | 37 | GroupName 38 | Tag 39 | -------------------------------------------------------------------------------- /functions/PercentAboveThreshold.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from datetime import timedelta 3 | import datetime 4 | #import sys 5 | 6 | #import os 7 | #import pickle 8 | 9 | #debug_logs_directory = 10 | 11 | class PercentAboveThreshold(): 12 | 13 | def __init__(self): 14 | self.name = 'Percent Above or Below Threshold' 15 | self.description = 'Calculates the percentage of pixels that are above or below' \ 16 | 'a threshold value. The threshold value is set in the raster function.' \ 17 | 'The raster function can be applied to a time-enabled stack of rasters in ' \ 18 | 'a mosaic dataset.' 19 | 20 | self.times = [] 21 | self.start_year = None 22 | self.end_year = None 23 | self.threshold = 50 24 | 25 | def getParameterInfo(self): 26 | return [ 27 | { 28 | 'name': 'rasters', 29 | 'dataType': 'rasters', 30 | 'value': None, 31 | 'required': True, 32 | 'displayName': 'Rasters', 33 | 'description': 'The collection of rasters to analyze.', 34 | }, 35 | { 36 | 'name': 'start_date', 37 | 'dataType': 'string', 38 | 'value': '1/1/2019 12:30:00', 39 | 'required': True, 40 | 'displayName': 'Start Date', 41 | 'description': 'The beginning date of analysis (inclusive of entire year).', 42 | }, 43 | { 44 | 'name': 'end_date', 45 | 'dataType': 'string', 46 | 'value': '12/31/2019 23:30:00', 47 | 'required': True, 48 | 'displayName': 'End Date', 49 | 'description': 'The final date of analysis (inclusive of entire year).', 50 | }, 51 | { 52 | 'name': 'threshold', 53 | 'dataType': 'numeric', 54 | 'value': 45, 55 | 'required': True, 56 | 'displayName': 'Value Threshold', 57 | 'description': 'Value Threshold.', 58 | } 59 | ] 60 | 61 | def getConfiguration(self, **scalars): 62 | return { 63 | 'inheritProperties': 4 | 8, # inherit everything but the pixel type (1) and NoData (2) 64 | 'invalidateProperties': 2 | 4, # invalidate histogram and statistics because we are modifying pixel values 65 | 'inputMask': True, # need raster mask of all input rasters in .updatePixels(). 66 | 'resampling': False, # process at native resolution 67 | 'keyMetadata': ['AcquisitionDate'] 68 | } 69 | 70 | def updateRasterInfo(self, **kwargs): 71 | # outStats = {'minimum': -1, 'maximum': 1} 72 | # outStatsTuple = tuple(outStats for i in range(outBandCount)) 73 | 74 | kwargs['output_info']['pixelType'] = 'f4' # output pixels are floating-point values 75 | kwargs['output_info']['histogram'] = () # no statistics/histogram for output raster specified 76 | kwargs['output_info']['statistics'] = () # outStatsTuple 77 | #kwargs['output_info'][ 78 | # 'bandCount'] = outBandCount # number of output bands. 7 time bands, 3 TC bands, creates 21 bands 79 | 80 | self.times = kwargs['rasters_keyMetadata'] 81 | self.start_date = kwargs['start_date'] 82 | self.end_date = kwargs['end_date'] 83 | self.threshold = int(kwargs['threshold']) 84 | 85 | return kwargs 86 | 87 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 88 | return keyMetadata 89 | 90 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 91 | 92 | #fname = '{:%Y_%b_%d_%H_%M_%S}_t.txt'.format(datetime.datetime.now()) 93 | #filename = os.path.join(debug_logs_directory, fname) 94 | 95 | #file = open(filename,"w") 96 | #file.write("File Open.\n") 97 | 98 | pix_time = [j['acquisitiondate'] for j in self.times] 99 | 100 | 101 | #pickle_filename = os.path.join(debug_logs_directory, fname) 102 | #pickle.dump(pix_time, open(pickle_filename[:-4]+'pix_time.p',"wb")) 103 | 104 | #file.write(str(len(pix_time))+ "\n") 105 | 106 | pix_blocks = pixelBlocks['rasters_pixels'] 107 | pix_array = np.asarray(pix_blocks) 108 | 109 | #pickle_filename = os.path.join(debug_logs_directory, fname) 110 | #pickle.dump(pix_array, open(pickle_filename[:-4]+'pix_blocks.p',"wb")) 111 | 112 | pix_array_dim = pix_array.shape 113 | num_squares_x = pix_array_dim[2] 114 | num_squares_y = pix_array_dim[3] 115 | 116 | #file.write("Filtering Based on Time\n") 117 | 118 | # This worked before I added time Filtering: 119 | #pix_as_array = np.reshape(pix_array, -1) 120 | #total_count = np.size(pix_as_array) 121 | #vals_above_thresh_count = np.size(np.where(pix_as_array <= self.threshold)) 122 | #outBlock = np.ones((num_squares_x, num_squares_y)) * (vals_above_thresh_count / total_count) * 100 123 | 124 | t_array = [] 125 | ind_array = [] 126 | start_date = self.start_date #"1/1/2019 12:30:00" 127 | end_date = self.end_date #"7/7/2019 12:30:00" 128 | start_datetime = datetime.datetime.strptime(start_date, '%m/%d/%Y %H:%M:%S') # %p') 129 | end_datetime = datetime.datetime.strptime(end_date, '%m/%d/%Y %H:%M:%S') # %p') 130 | for ind, time in enumerate(pix_time): 131 | temp_t = datetime.datetime(1900, 1, 1) + timedelta(time - 2) 132 | if temp_t >= start_datetime and temp_t <= end_datetime: 133 | t_array.append(temp_t) 134 | ind_array.append(ind) 135 | 136 | #time_within = [pix_time[x] for x in ind_array] 137 | pix_array_within = pix_array[ind_array, :, :, :] 138 | 139 | #threshold = 50 140 | pix_as_array = np.reshape(pix_array_within, -1) 141 | total_count = np.size(pix_as_array) 142 | vals_above_thresh_count = np.size(np.where(pix_as_array <= self.threshold)) #< below, > above 143 | outBlock = np.ones((num_squares_x, num_squares_y)) * (vals_above_thresh_count / total_count) * 100 144 | 145 | #file.write("DONE\n") 146 | #file.close() 147 | pixelBlocks['output_pixels'] = outBlock.astype(props['pixelType'], copy=False) 148 | #masks = np.array(pixelBlocks['rasters_mask'], copy=False) 149 | #pixelBlocks['output_mask'] = np.all(masks, axis=0).astype('u1', copy=False) 150 | return pixelBlocks 151 | -------------------------------------------------------------------------------- /functions/PercentAboveThreshold.rft.xml: -------------------------------------------------------------------------------- 1 | PercentAboveThresholdCalculates the percentage of pixels above a threshold value.Percent Above or Below ThresholdCalculates the percentage of pixels that are above or belowa threshold value. The threshold value is set in the raster function.The raster function can be applied to a time-enabled stack of rasters in a mosaic dataset.UNKNOWNrastersstart_dateend_datethresholdPythonModuleClassNamerasters__IsRasterArray__falsestart_date1/1/2019 12:30:00falseend_date12/31/2019 23:30:00falsethreshold50falseC:\PROJECTS\gbrunner-raster-functions\functions\PercentAboveThreshold.pyClassNamePercentAboveThresholdfalse__tans__(rasters,start_date,end_date,threshold)Calculates the percentage of pixels above a threshold value.2VariableOBJECTID -------------------------------------------------------------------------------- /functions/RandomForestClassifier.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from sklearn.ensemble import RandomForestClassifier 3 | import numpy as np 4 | 5 | ''' 6 | A random forest classifier. 7 | 8 | A random forest is a meta estimator that fits a number of decision tree classifiers on 9 | various sub-samples of the dataset and uses averaging to improve the 10 | predictive accuracy and control over-fitting. 11 | The sub-sample size is controlled with the max_samples parameter if bootstrap=True (default), 12 | otherwise the whole dataset is used to build each tree. 13 | 14 | https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html? 15 | highlight=random%20forest#sklearn.ensemble.RandomForestClassifier 16 | ''' 17 | 18 | # Note: Can not name the class RandomForestClassifier because it will conflict 19 | # with the scikit-learn class that is imported. 20 | class RandomForest(): 21 | def __init__(self): 22 | self.name = 'Random Forest Classifier' 23 | self.description = 'Random Forest Classifier implemented as a Python Raster Function' 24 | 25 | # inputs as string, but eventually will be numpy arrays 26 | self.df = None 27 | self.datafile = None 28 | self.threshold = 0.5 29 | 30 | 31 | def getParameterInfo(self): 32 | return [ 33 | { 34 | 'name': 'rasters', 35 | 'dataType': 'rasters', 36 | 'value': None, 37 | 'required': True, 38 | 'displayName': 'Input Rasters', 39 | 'description': 'Must be in the same order as the columns in the CSV file' 40 | }, 41 | { 42 | 'name': 'training_data_from_file', 43 | 'dataType': 'string', 44 | 'value': 'C:\\PROJECTS\\ML\\training_data.csv', 45 | 'required': True, 46 | 'displayName': 'Training data CSV filepath', 47 | 'description': 'Full filepath directory to training data CSV. ' 48 | 'Internally this will load from disk and be converted to a pandas dataframe.' 49 | } 50 | ] 51 | 52 | def getConfiguration(self, **scalars): 53 | return { 54 | 'inheritProperties': 1 | 2 | 4 | 8, # inherit all from the raster 55 | 'invalidateProperties': 2 | 4 | 8 # reset stats, histogram, key properties 56 | } 57 | 58 | def updateRasterInfo(self, **kwargs): 59 | 60 | # convert filepath string input param to numpy array 61 | self.datafile = str(kwargs['training_data_from_file']) 62 | #self.threshold = float(kwargs['threshold']) 63 | 64 | kwargs['output_info']['pixelType'] = 'f4' 65 | kwargs['output_info']['histogram'] = () 66 | kwargs['output_info']['statistics'] = () 67 | kwargs['output_info']['bandCount'] = 3 68 | 69 | return kwargs 70 | 71 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 72 | 73 | self.df = pd.read_csv(self.datafile) 74 | 75 | try: 76 | fields_to_drop = ['OBJECTID', 'LOCATION_X', 'LOCATION_Y'] 77 | self.df.drop(fields_to_drop, axis=1, inplace=True) 78 | except: 79 | pass 80 | 81 | y_val = 'VarToPredict' # The value that you probably want to predict 82 | x_train = self.df.loc[:, self.df.columns != y_val] 83 | y_train = self.df[y_val] 84 | x_train.fillna(0, inplace=True) 85 | y_train.fillna(0, inplace=True) 86 | 87 | # Initialize RandomForestClassifier 88 | # Recommend trying different values for: 89 | # - n_estimators 90 | # - max_features 91 | # - random_state 92 | regr1 = RandomForestClassifier(n_estimators=20, random_state=0) # max_features=13, random_state=1) 93 | regr1.fit(x_train, y_train) 94 | 95 | pix_blocks = pixelBlocks['rasters_pixels'] 96 | pix_array = np.asarray(pix_blocks) 97 | pix_array = np.squeeze(pix_array) 98 | 99 | pixels_reshaped = pix_array.reshape(pix_array.shape[0], -1).transpose() 100 | 101 | # Run RandomForestRegressor 102 | pred = regr1.predict(pixels_reshaped) 103 | pred_proba = regr1.predict_proba(pixels_reshaped) 104 | 105 | res = np.hstack([np.expand_dims(pred, 1), pred_proba]) 106 | 107 | #res = pred.reshape((pix_array.shape[1], pix_array.shape[2])) 108 | res_reshape = np.reshape( 109 | res.transpose(), 110 | (3, pix_array.shape[1], pix_array.shape[2]) 111 | ) 112 | 113 | res_reshape[res_reshape <= self.threshold] = 0 114 | 115 | pixelBlocks['output_pixels'] = res_reshape.astype( 116 | props['pixelType'], 117 | copy=True 118 | ) 119 | 120 | return pixelBlocks 121 | 122 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 123 | return keyMetadata 124 | -------------------------------------------------------------------------------- /functions/RankFilter.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from skimage.transform import resize 3 | from skimage.util import view_as_blocks 4 | from skimage.filters import rank 5 | from skimage.morphology import square 6 | from utils import Trace 7 | 8 | 9 | class RankFilter(): 10 | 11 | def __init__(self): 12 | self.name = "Rank Filter Function" 13 | self.description = ("Apply a non-linear filter on a local neighborhood of inputs pixels in a sliding window.") 14 | self.func = rank.mean 15 | self.window = None 16 | self.trace = Trace() 17 | self.padding = 0 18 | 19 | def getParameterInfo(self): 20 | return [ 21 | { 22 | 'name': 'raster', 23 | 'dataType': 'raster', 24 | 'value': None, 25 | 'required': True, 26 | 'displayName': "Input Raster", 27 | 'description': ("The primary input raster on which the filter is applied.") 28 | }, 29 | { 30 | 'name': 'measure', 31 | 'dataType': 'string', 32 | 'value': 'Mean', 33 | 'required': False, 34 | 'displayName': "Measure", 35 | 'domain': ('Minimum', 'Maximum', 'Mean', 'Bilateral Mean', 'Median', 36 | 'Sum', 'Entropy', 'Threshold', 'Autolevel'), 37 | 'description': ("The measure represented by an ouput pixel " 38 | "computed over a sliding window of input pixels.") 39 | }, 40 | { 41 | 'name': 'size', 42 | 'dataType': 'numeric', 43 | 'value': 5, 44 | 'required': False, 45 | 'displayName': "Window Size", 46 | 'description': ("The width of the sliding window or kernel (in pixels).") 47 | }, 48 | { 49 | 'name': 'res', 50 | 'dataType': 'string', 51 | 'value': 'Request', 52 | 'required': False, 53 | 'displayName': "Resolution", 54 | 'domain': ('Request', 'Raster'), 55 | 'description': ("The resolution at which the filter is applied. " 56 | "Choose between processing input pixels at resampled display/request resolution " 57 | "or in the original/raster resolution.") 58 | }, 59 | ] 60 | 61 | def getConfiguration(self, **scalars): 62 | r = scalars.get('res', None) 63 | s = scalars.get('size', None) 64 | s = 3 if s is None else s 65 | self.padding = int(s / 2) 66 | 67 | return { 68 | 'inheritProperties': 4 | 8, # inherit everything but the pixel type (1) and NoData (2) 69 | 'invalidateProperties': 2 | 4 | 8, # invalidate histogram, statistics, and key metadata 70 | 'inputMask': True, 71 | 'padding': self.padding, 72 | 'resampling': not(r is not None and str(r).lower() == 'raster') 73 | } 74 | 75 | def updateRasterInfo(self, **kwargs): 76 | kwargs['output_info']['statistics'] = () 77 | kwargs['output_info']['histogram'] = () 78 | 79 | self.window = square(int(kwargs.get('size', 3))) 80 | m = kwargs.get('measure', 'Mean').lower() 81 | if m == 'minimum': 82 | self.func = rank.minimum 83 | elif m == 'maximum': 84 | self.func = rank.maximum 85 | elif m == 'mean': 86 | self.func = rank.mean 87 | elif m == 'bilateral mean': 88 | self.func = rank.mean_bilateral 89 | elif m == 'median': 90 | self.func = rank.median 91 | elif m == 'sum': 92 | self.func = rank.sum 93 | elif m == 'entropy': 94 | self.func = rank.entropy 95 | elif m == 'threshold': 96 | self.func = rank.threshold 97 | elif m == 'autolevel': 98 | self.func = rank.autolevel 99 | return kwargs 100 | 101 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 102 | p = pixelBlocks['raster_pixels'] 103 | m = pixelBlocks['raster_mask'] 104 | 105 | q = np.empty(p.shape) 106 | for b in range(p.shape[0]): 107 | q[b] = self.func(p[b], selem=self.window, mask=m[b]) 108 | 109 | d = self.padding 110 | pixelBlocks['output_pixels'] = q[0][d:-d, d:-d].astype(props['pixelType'], copy=False) 111 | return pixelBlocks 112 | 113 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 114 | if bandIndex == -1: 115 | keyMetadata['datatype'] = 'Processed' 116 | return keyMetadata 117 | 118 | 119 | # ----- ## ----- ## ----- ## ----- ## ----- ## ----- ## ----- ## ----- ## 120 | 121 | """ 122 | References: 123 | 124 | [1] Sicuranza, G., 2000. Nonlinear image processing. Academic Press. 125 | 126 | [2] Rank Filters for Image Processing. 127 | http://www.numerical-tours.com/matlab/denoisingadv_7_rankfilters/ 128 | 129 | [3] Scikit-image: Image processing in Python (Rank filters). 130 | http://scikit-image.org/docs/dev/auto_examples/applications/plot_rank_filters.html 131 | """ 132 | -------------------------------------------------------------------------------- /functions/RankFilter.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | RankFilter 3 | A raster function template. 4 | 5 | Rank Filter Function 6 | Apply a non-linear filter on a local neighborhood of inputs pixels in a sliding window. 7 | UNKNOWN 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | raster 14 | measure 15 | size 16 | res 17 | 18 | 19 | RankFilter.py 20 | RankFilter 21 | 22 | Raster 23 | 24 | 25 | true 26 | 27 | 28 | measure 29 | 30 | Mean 31 | false 32 | 33 | 34 | size 35 | 36 | 5 37 | false 38 | 39 | 40 | res 41 | 42 | Request 43 | false 44 | 45 | 46 | 47 | 48 | 0 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /functions/ReplaceNulls.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class ReplaceNulls(): 4 | def __init__(self): 5 | self.name = 'Replace Nulls' 6 | self.description = 'Replace NULL values in a raster with a user defined value.' 7 | 8 | 9 | 10 | def getParameterInfo(self): 11 | return [ 12 | { 13 | 'name': 'raster', 14 | 'dataType': 'raster', 15 | 'value':'Multiband Raster', 16 | 'required': True, 17 | 'displayName': 'Input Raster', 18 | 'description': 'Input Raster' 19 | }, 20 | { 21 | 'name': 'fill_val', 22 | 'dataType': 'numeric', 23 | 'value': 1, 24 | 'required': True, 25 | 'displayName': 'Replace Value', 26 | 'description': 'Value to replace with' 27 | } 28 | ] 29 | 30 | def getConfiguration(self, **scalars): 31 | return { 32 | 'inheritProperties': 1 | 2 | 4 | 8, # inherit everything but the pixel type (1) and NoData (2) 33 | #'invalidateProperties': 1 | 2 | 4 | 8, # invalidate histogram and statistics because we are modifying pixel values 34 | 'resampling': False 35 | } 36 | 37 | def updateRasterInfo(self, **kwargs): 38 | 39 | self.fill_val = float(kwargs['fill_val']) 40 | # repeat stats for all output raster bands 41 | #kwargs['output_info']['statistics'] = tuple(outStats for i in range(self.out_band_count)) 42 | kwargs['output_info']['pixelType'] = 'f4' 43 | kwargs['output_info']['statistics'] = () 44 | 45 | return kwargs 46 | 47 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 48 | 49 | pix_array = np.asarray(pixelBlocks['raster_pixels']) 50 | np.place(pix_array, pix_array==0, [self.fill_val]) 51 | 52 | mask = np.ones(pix_array.shape) 53 | pixelBlocks['output_mask'] = mask.astype('u1', copy = False) 54 | pixelBlocks['output_pixels'] = pix_array.astype(props['pixelType'], copy=True) 55 | 56 | 57 | return pixelBlocks 58 | 59 | 60 | -------------------------------------------------------------------------------- /functions/ReplaceNulls.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | ReplaceNulls 4 | Replace Nulls 5 | 6 | Replace Nulls 7 | Replace NULL values in a raster with a user defined value. 8 | UNKNOWN 9 | 10 | 11 | 12 | raster 13 | fill_val 14 | PythonModule 15 | ClassName 16 | 17 | 18 | 19 | Raster 20 | 21 | Multiband Raster 22 | true 23 | 24 | 25 | fill_val 26 | 27 | 1 28 | false 29 | 30 | C:\PROJECTS\gbrunner-raster-functions\functions\ReplaceNulls.py 31 | 32 | ClassName 33 | 34 | ReplaceNulls 35 | false 36 | 37 | 38 | 39 | 40 | __tans__(raster,fill_val) 41 | 42 | Replace Nulls 43 | 0 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /functions/SeasonalARIMA.rft.xml: -------------------------------------------------------------------------------- 1 | Seasonal ARIMASeasonal ARIMASeasonal ARIMAThis function predicts the change in the observed pixel value given a time series of values.UNKNOWNrastersdata_start_yeartrain_start_yeartrain_end_yearpredict_yearpredict_monthseasonal_orderPythonModuleClassNamerasters__IsRasterArray__falsedata_start_year1980falsetrain_start_year1980falsetrain_end_year2010falsepredict_year2050falsepredict_monthJunfalseseasonal_order0, 1, 1, 12falseC:\PROJECTS\gbrunner-raster-functions\functions\SeasonalARIMA.pyClassNameSeasonalARIMAfalse__tans__(rasters,data_start_year,train_start_year,train_end_year,predict_year,predict_month,seasonal_order)2VariableOBJECTID -------------------------------------------------------------------------------- /functions/SelectByPixelSize.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import utils 3 | 4 | class SelectByPixelSize(): 5 | 6 | def __init__(self): 7 | self.name = "Select by Pixel Size" 8 | self.description = "This function returns pixels associated with one of two input rasters based on the request resolution." 9 | self.threshold = 0.0 10 | self.inBands1, self.inBands2, self.outBands = 1, 1, 1 11 | self.trace = utils.Trace() 12 | 13 | def getParameterInfo(self): 14 | return [ 15 | { 16 | 'name': 'r1', 17 | 'dataType': 'raster', 18 | 'value': None, 19 | 'required': True, 20 | 'displayName': "Raster 1", 21 | 'description': ("The raster that's returned when request cell size is lower than " 22 | "the 'Cell Size Threshold'. A lower cell size value implies finer resolution.") 23 | }, 24 | { 25 | 'name': 'r2', 26 | 'dataType': 'raster', 27 | 'value': None, 28 | 'required': True, 29 | 'displayName': "Raster 2", 30 | 'description': ("The raster that's returned when request cell size is higher than or equal to " 31 | "the 'Cell Size Threshold'. A higher cell size value implies coarser resolution.") 32 | }, 33 | { 34 | 'name': 'threshold', 35 | 'dataType': 'numeric', 36 | 'value': 0.0, 37 | 'required': True, 38 | 'displayName': "Cell Size Threshold", 39 | 'description': ("The cell size threshold that controls which of the two input " 40 | "rasters contributes pixels to the output.") 41 | }, 42 | ] 43 | 44 | def getConfiguration(self, **scalars): 45 | return { 46 | 'inputMask': True, 47 | 'resampling': True 48 | } 49 | 50 | def updateRasterInfo(self, **kwargs): 51 | self.threshold = float(kwargs.get('threshold', 0.0)) 52 | if self.threshold <= 0.0: 53 | self.threshold = np.mean((np.mean(kwargs['r1_info']['cellSize']), np.mean(kwargs['r2_info']['cellSize']))) 54 | 55 | self.inBands1 = kwargs['r1_info']['bandCount'] 56 | self.inBands2 = kwargs['r2_info']['bandCount'] 57 | kwargs['output_info']['bandCount'] = min(self.inBands1, self.inBands2) 58 | kwargs['output_info']['statistics'] = () 59 | kwargs['output_info']['histogram'] = () 60 | 61 | self.trace.log("Trace|Threshold cell-size|{0}\n".format(self.threshold)) 62 | self.trace.log("Trace|output_info|{0}\n".format(kwargs['output_info'])) 63 | return kwargs 64 | 65 | def selectRasters(self, tlc, shape, props): 66 | cellSize = props['cellSize'] 67 | v = 0.5 * (cellSize[0] + cellSize[1]) 68 | if v < self.threshold: 69 | return ('r1',) 70 | else: return ('r2',) 71 | 72 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 73 | cellSize = props['cellSize'] 74 | v = 0.5 * (cellSize[0] + cellSize[1]) 75 | self.trace.log("Trace|Request cell-size|{0}\n".format(v)) 76 | 77 | if v < self.threshold: 78 | sPixels = 'r1_pixels' 79 | sMask = 'r1_mask' 80 | nBands = self.inBands1 81 | else: 82 | sPixels = 'r2_pixels' 83 | sMask = 'r2_mask' 84 | nBands = self.inBands2 85 | 86 | if self.outBands == nBands: 87 | p = pixelBlocks[sPixels] 88 | m = pixelBlocks[sMask] 89 | else: 90 | p = pixelBlocks[sPixels][0:self.outBands, :, :] 91 | m = pixelBlocks[sMask][0:self.outBands, :, :] 92 | 93 | pixelBlocks['output_pixels'] = p.astype(props['pixelType']) 94 | pixelBlocks['output_mask'] = m.astype('u1') 95 | return pixelBlocks 96 | -------------------------------------------------------------------------------- /functions/SelectByPixelSize.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | SelectByPixelSize 3 | A raster function template. 4 | 5 | Select by Pixel Size 6 | 7 | UNKNOWN 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | threshold 14 | r1 15 | r2 16 | 17 | 18 | SelectByPixelSize.py 19 | SelectByPixelSize 20 | 0 21 | 22 | Raster1 23 | 24 | 25 | true 26 | 27 | 28 | Raster2 29 | 30 | 31 | true 32 | 33 | 34 | 35 | 36 | 2 37 | 38 | 39 | GroupName 40 | Tag 41 | 42 | -------------------------------------------------------------------------------- /functions/StepwiseLocalRadiometricAdjustment.rft.xml: -------------------------------------------------------------------------------- 1 | Stepwise Local Radiometric AdjustmentPython raster function for blending areas using Stepwise Local Radiometric Adjustment algorithm.The stepwise local radiometric adjustment is undertaken to fill contaminated areas and is conducted on each mask region of the target imageStepwise Local Radiometric AdjustmentPython raster function for blending areas using Stepwise Local Radiometric Adjustment algorithm.The stepwise local radiometric adjustment is undertaken to fill contaminated areas and is conducted on each mask region of the target imageUNKNOWNinput_rasterinput_replacement_rasterinput_masksize_of_windowPythonModuleClassNameRastertrueinput_replacement_rastertrueinput_masktruesize_of_window80falseC:\Esri_project\cloud_masking_algo\python_raster_fucntion\StepwiseLocalRadiometricAdjustment.pyClassNameStepwiseLocalRadiometricAdjustmentfalse0MatchVariableMatchVariable1falseUnionDimensionUnionDimension0false -------------------------------------------------------------------------------- /functions/VF.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | VF 3 | A raster function template. 4 | 5 | Vector Field Function 6 | Combines magnitude(u) and direction(v) rasters in scientific datasets to produce a two band output. 7 | UNKNOWN 8 | 9 | 10 | 11 | Raster1 12 | Raster2 13 | InputDataType 14 | OutputDataType 15 | AngleReferenceSystem 16 | __Properties 17 | 18 | 19 | 20 | Raster1 21 | 22 | 23 | true 24 | 25 | 26 | Raster2 27 | 28 | 29 | true 30 | 31 | 32 | InputDataType 33 | 34 | Vector-UV 35 | false 36 | 37 | 38 | OutputDataType 39 | 40 | Vector-MagDir 41 | false 42 | 43 | 44 | AngleReferenceSystem 45 | 46 | 1 47 | false 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 0 56 | 57 | 58 | GroupName 59 | Tag 60 | 61 | -------------------------------------------------------------------------------- /functions/VineyardAnalysis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class VineyardAnalysis(): 5 | 6 | def __init__(self): 7 | self.name = "Vineyard Suitability Analysis Function" 8 | self.description = "This function computes vineyard suitability given elevation, slope, aspect, and soil-type rasters." 9 | 10 | def getParameterInfo(self): 11 | return [ 12 | { 13 | 'name': 'elevation', 14 | 'dataType': 'raster', 15 | 'value': None, 16 | 'required': True, 17 | 'displayName': "Elevation Raster", 18 | 'description': "The primary single-band raster where pixel values represent elevation in meters." 19 | }, 20 | { 21 | 'name': 'slope', 22 | 'dataType': 'raster', 23 | 'value': None, 24 | 'required': True, 25 | 'displayName': "Slope Raster", 26 | 'description': "A single-band raster where pixel values represent slope." 27 | }, 28 | { 29 | 'name': 'aspect', 30 | 'dataType': 'raster', 31 | 'value': None, 32 | 'required': True, 33 | 'displayName': "Aspect Raster", 34 | 'description': "A single-band raster where pixel values represent aspect." 35 | }, 36 | { 37 | 'name': 'soiltype', 38 | 'dataType': 'raster', 39 | 'value': None, 40 | 'required': False, 41 | 'displayName': "Soil Type Raster", 42 | 'description': "A single-band thematic raster where pixel values represent soil type." 43 | }, 44 | ] 45 | 46 | def getConfiguration(self, **scalars): 47 | return { 48 | 'inheritProperties': 2 | 4 | 8, # inherit all but the pixel type from the input raster 49 | 'invalidateProperties': 2 | 4 | 8, # reset any statistics and histogram that might be held by 50 | # the parent dataset (because this function modifies pixel values). 51 | 'inputMask': True # We need the input raster mask in .updatePixels(). 52 | } 53 | 54 | def updateRasterInfo(self, **kwargs): 55 | kwargs['output_info']['bandCount'] = 1 56 | kwargs['output_info']['pixelType'] = 'u1' 57 | kwargs['output_info']['statistics'] = ({'minimum': 0, 'maximum': 3}, ) 58 | kwargs['output_info']['noData'] = np.array([0], 'u1') 59 | return kwargs 60 | 61 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 62 | elev = np.array(pixelBlocks['elevation_pixels'], dtype='f4', copy=False) 63 | slope = np.array(pixelBlocks['slope_pixels'], dtype='f4', copy=False) 64 | aspect = np.array(pixelBlocks['aspect_pixels'], dtype='f4', copy=False) 65 | # soil = np.array(pixelBlocks['soiltype_pixels'], 'i8') 66 | 67 | E = (elev > 30).astype('u1', copy=False) & (elev < 400).astype('u1', copy=False) 68 | S = (slope > 5).astype('u1', copy=False) & (slope < 60).astype('u1', copy=False) 69 | A = (aspect > 0).astype('u1', copy=False) & (aspect < 200).astype('u1', copy=False) 70 | pixelBlocks['output_pixels'] = (E + S + A).astype(props['pixelType'], copy=False) 71 | return pixelBlocks 72 | 73 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 74 | if bandIndex == -1: 75 | keyMetadata['datatype'] = 'Scientific' 76 | keyMetadata['variable'] = 'VineyardSuitability' 77 | elif bandIndex == 0: 78 | keyMetadata['wavelengthmin'] = None # reset inapplicable band-specific key metadata 79 | keyMetadata['wavelengthmax'] = None 80 | keyMetadata['bandname'] = 'VineyardSuitability' 81 | return keyMetadata 82 | -------------------------------------------------------------------------------- /functions/VineyardAnalysis.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | VineyardAnalysis 3 | A raster function template. 4 | 5 | Python Adapter Function 6 | Adapter function for raster functions written in python. 7 | UNKNOWN 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | elevation 14 | slope 15 | aspect 16 | 17 | 18 | VineyardAnalysis.py 19 | VineyardAnalysis 20 | 21 | Raster 22 | 23 | 24 | true 25 | 26 | 27 | Raster Function Template 28 | A raster function template. 29 | 30 | Slope Function 31 | Calculates the rate of change of elevation for each DEM cell. 32 | UNKNOWN 33 | 34 | 35 | 36 | DEM 37 | ZFactor 38 | PSPower 39 | PSZFactor 40 | SlopeType 41 | RemoveEdgeEffect 42 | 43 | 44 | 45 | Raster 46 | 47 | 48 | true 49 | 50 | 51 | ZFactor_201499_0627_233 52 | 53 | 1 54 | false 55 | 56 | 57 | PSPower_201499_0627_233 58 | 59 | 0.66400000000000003 60 | false 61 | 62 | 63 | PSZFactor_201499_0627_233 64 | 65 | 0.024 66 | false 67 | 68 | 69 | SlopeType_201499_0627_233 70 | 71 | 1 72 | false 73 | 74 | 75 | RemoveEdgeEffect_201499_0627_233 76 | 77 | false 78 | false 79 | 80 | 81 | 82 | 83 | 0 84 | 85 | 86 | 87 | 88 | 89 | 90 | Raster Function Template 91 | A raster function template. 92 | 93 | Aspect Function 94 | Identifies the downslope direction of the maximum rate of change in value from each cell to its neighbors. 95 | UNKNOWN 96 | 97 | 98 | Raster 99 | 100 | 101 | true 102 | 103 | 104 | 0 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 0 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /functions/deprecated/Aggregate.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class Aggregate(): 5 | 6 | def __init__(self): 7 | self.name = "Aggregate Rasters Function" 8 | self.description = "This function aggregates pixel values over a collection of overlapping single-band rasters." 9 | self.operator = np.sum 10 | 11 | def getParameterInfo(self): 12 | return [ 13 | { 14 | 'name': 'rasters', 15 | 'dataType': 'rasters', 16 | 'value': None, 17 | 'required': True, 18 | 'displayName': "Rasters", 19 | 'description': "The collection of overlapping rasters to aggregate.", 20 | }, 21 | { 22 | 'name': 'method', 23 | 'dataType': 'string', 24 | 'value': 'Sum', 25 | 'required': False, 26 | 'displayName': "Method", 27 | 'domain': ('Sum', 'Average', 'Median', 'Standard Deviation', 'Minimum', 'Maximum'), 28 | 'description': "The method indicating how overlapping pixels of the input rasters are aggregated.", 29 | }, 30 | ] 31 | 32 | def getConfiguration(self, **scalars): 33 | m = scalars.get('method', 'Sum').lower() 34 | if m == 'average': self.operator = np.mean 35 | elif m == 'median': self.operator = np.median 36 | elif m == 'minimum': self.operator = np.min 37 | elif m == 'maximum': self.operator = np.max 38 | elif m == 'standard deviation': self.operator = np.std 39 | else: self.operator = np.sum 40 | 41 | return { 42 | 'inheritProperties': 4 | 8, # inherit everything but the pixel type (1) and NoData (2) 43 | 'invalidateProperties': 2 | 4, # invalidate histogram and statistics because we are modifying pixel values 44 | 'inputMask': True, # need raster mask of all input rasters in .updatePixels(). 45 | 'resampling': False # process at native resolution 46 | 47 | } 48 | 49 | def updateRasterInfo(self, **kwargs): 50 | kwargs['output_info']['pixelType'] = 'f4' # output pixels are floating-point values 51 | kwargs['output_info']['noData'] = None # we'll set the mask updatePixels() 52 | kwargs['output_info']['histogram'] = () # no statistics/histogram for output raster specified 53 | kwargs['output_info']['statistics'] = () 54 | return kwargs 55 | 56 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 57 | # pixelBlocks['rasters_pixels']: tuple of 3-d array containing pixel blocks from each input raster 58 | # apply the selected operator over each array in the tuple 59 | outBlock = self.operator(pixelBlocks['rasters_pixels'], axis=0) 60 | pixelBlocks['output_pixels'] = outBlock.astype(props['pixelType'], copy=False) 61 | masks = np.array(pixelBlocks['rasters_mask'], copy=False) 62 | pixelBlocks['output_mask'] = np.all(masks, axis=0).astype('u1', copy=False) 63 | return pixelBlocks 64 | -------------------------------------------------------------------------------- /functions/deprecated/Aggregate.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Aggregate 4 | A raster function template. 5 | 6 | Aggregate Rasters Function 7 | This function aggregates pixel values over a collection of overlapping single-band rasters. 8 | F32 9 | 10 | 11 | 12 | PythonModule 13 | Rasters 14 | ClassName 15 | method 16 | 17 | 18 | Aggregate.py 19 | 20 | Raster[] 21 | The set of overlapping rasters to aggregate. 22 | 23 | false 24 | 25 | Aggregate 26 | 27 | method 28 | 29 | Average 30 | false 31 | 32 | 33 | 34 | 35 | 2 36 | 37 | 38 | GroupName 39 | Tag 40 | 41 | -------------------------------------------------------------------------------- /functions/deprecated/Arithmetic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class Arithmetic(): 5 | def __init__(self): 6 | self.name = "Arithmetic Function" 7 | self.description = "Performs simple arithmetic operations on two rasters." 8 | self.op = None 9 | 10 | def getParameterInfo(self): 11 | return [ 12 | { 13 | 'name': 'r1', 14 | 'dataType': 'raster', 15 | 'value': None, 16 | 'required': True, 17 | 'displayName': "Raster A", 18 | 'description': "" 19 | }, 20 | { 21 | 'name': 'r2', 22 | 'dataType': 'raster', 23 | 'value': None, 24 | 'required': True, 25 | 'displayName': "Raster B", 26 | 'description': "" 27 | }, 28 | { 29 | 'name': 'op', 30 | 'dataType': 'string', 31 | 'value': 'Add', 32 | 'required': False, 33 | 'domain': ('Add', 'Subtract', 'Multiply', 'Divide'), 34 | 'displayName': "Operation", 35 | 'description': "" 36 | }, 37 | ] 38 | 39 | def getConfiguration(self, **scalars): 40 | return { 41 | 'inheritProperties': 2 | 4 | 8, 42 | 'invalidateProperties': 2 | 4 | 8, 43 | 'resampling': True # process at request resolution 44 | } 45 | 46 | def updateRasterInfo(self, **kwargs): 47 | m = kwargs.get('op', 'Add').lower() 48 | 49 | if m == 'add': self.op = np.add 50 | elif m == 'subtract': self.op = np.subtract 51 | elif m == 'multiply': self.op = np.multiply 52 | elif m == 'divide': self.op = np.divide 53 | 54 | kwargs['output_info']['statistics'] = () 55 | kwargs['output_info']['histogram'] = () 56 | return kwargs 57 | 58 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 59 | r1 = np.array(pixelBlocks['r1_pixels'], dtype='f4', copy=False) 60 | r2 = np.array(pixelBlocks['r2_pixels'], dtype='f4', copy=False) 61 | 62 | np.seterr(divide='ignore') 63 | pixelBlocks['output_pixels'] = self.op(r1, r2).astype(props['pixelType'], copy=False) 64 | return pixelBlocks 65 | 66 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 67 | if bandIndex == -1: 68 | keyMetadata['datatype'] = 'Processed' # outgoing raster is now 'Processed' 69 | elif bandIndex == 0: 70 | keyMetadata['wavelengthmin'] = None # reset inapplicable band-specific key metadata 71 | keyMetadata['wavelengthmax'] = None 72 | return keyMetadata 73 | -------------------------------------------------------------------------------- /functions/deprecated/CompositeBands.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Composite 4 | A raster function template. 5 | 6 | Composite Band Function 7 | Combines rasters to form a multiband raster. 8 | UNKNOWN 9 | 10 | 11 | Raster[] 12 | The collection of all input rasters. 13 | 14 | false 15 | 16 | 17 | 2 18 | 19 | 20 | GroupName 21 | Tag 22 | -------------------------------------------------------------------------------- /functions/deprecated/ConvertPerSecondToPerMonth.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from datetime import datetime 3 | from calendar import monthrange 4 | 5 | 6 | class ConvertPerSecondToPerMonth(): 7 | 8 | def __init__(self): 9 | self.name = "Convert Per-Second To Per-Month" 10 | self.description = ("This function converts a raster containing values " 11 | "in units-per-second to a raster representing units-per-month.") 12 | self.scaleFactor = 1.0 13 | self.unit = "units per month" 14 | 15 | def getParameterInfo(self): 16 | return [ 17 | { 18 | 'name': 'raster', 19 | 'dataType': 'raster', 20 | 'value': None, 21 | 'required': True, 22 | 'displayName': "Raster", 23 | 'description': "The primary input raster." 24 | }, 25 | { 26 | 'name': 'units', 27 | 'dataType': 'string', 28 | 'value': "per month", 29 | 'required': False, 30 | 'displayName': "Output Units", 31 | 'description': "Description of the units associated with the outgoing raster." 32 | }, 33 | ] 34 | 35 | def getConfiguration(self, **scalars): 36 | return { 37 | 'compositeRasters': False, # input is a single raster, band compositing doesn't apply. 38 | 'inheritProperties': 2 | 4 | 8, # inherit all but the pixel type 39 | 'invalidateProperties': 2 | 4 | 8, # reset statistics and histogram 40 | 'keyMetadata': ('stdtime', 'acquisitiondate'), # we can use this key property in .updateRasterInfo() 41 | } 42 | 43 | def updateRasterInfo(self, **kwargs): 44 | kwargs['output_info']['statistics'] = () 45 | kwargs['output_info']['histogram'] = () 46 | if kwargs['raster_info']['pixelType'] != 'f8': 47 | kwargs['output_info']['pixelType'] = 'f4' 48 | 49 | d = kwargs['raster_keyMetadata'].get('acquisitiondate', None) 50 | d = kwargs['raster_keyMetadata'].get('stdtime', d) 51 | if d is None: 52 | raise Exception("Unable to obtain date-time associated with the input raster using " 53 | "key metadata 'AcquisitionDate' or 'StdTime'.") 54 | 55 | dt, r = None, None 56 | if isinstance(d, float): 57 | dt = datetime.utcfromtimestamp((d - 25569.) * 86400.) # convert from variant time to Unix time 58 | elif isinstance(d, str) and d is not None and len(d) > 0: 59 | dt = datetime.strptime(d[:18], "%Y-%m-%dT%H:%M:%S") 60 | 61 | if dt is None: 62 | raise Exception("Unable to compute scale factor using the date '{0}' obtained from the input raster.".format(d)) 63 | 64 | r = monthrange(dt.year, dt.month) 65 | if len(r) != 2 or not isinstance(r[1], int): 66 | raise Exception("Unable to compute scale factor using the date '{0}' obtained from the input raster.".format(d)) 67 | 68 | self.scaleFactor = float(r[1]) * 86400. 69 | self.unit = kwargs.get('units', "per month") 70 | return kwargs 71 | 72 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 73 | inBlock = pixelBlocks['raster_pixels'] 74 | pixelBlocks['output_pixels'] = np.round(inBlock * self.scaleFactor).astype(props['pixelType'], copy=False) 75 | return pixelBlocks 76 | 77 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 78 | if bandIndex == -1: 79 | keyMetadata['unit'] = self.unit 80 | return keyMetadata 81 | -------------------------------------------------------------------------------- /functions/deprecated/ConvertPerSecondToPerMonth.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | ConvertPerSecondToPerMonth 3 | A raster function template. 4 | 5 | Convert Per-Second To Per-Month 6 | This function converts a raster containing values in units-per-second to a raster representing units-per-month. 7 | UNKNOWN 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | raster 14 | units 15 | 16 | 17 | ConvertPerSecondToPerMonth.py 18 | ConvertPerSecondToPerMonth 19 | 20 | Raster 21 | 22 | 23 | true 24 | 25 | 26 | units 27 | 28 | per month 29 | false 30 | 31 | 32 | 33 | 34 | 1 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /functions/deprecated/DeviationFromMean.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | Deviation from Mean 3 | Computes anomaly across a collection of rasters as indicated by the amount of deviation from the average. 4 | 5 | Arithmetic 6 | Adapter function for raster functions written in python. 7 | F32 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | op 14 | r1 15 | r2 16 | method 17 | 18 | 19 | Arithmetic.py 20 | Arithmetic 21 | Subtract 22 | 23 | Raster 24 | 25 | true 26 | 27 | 28 | Raster Function Template 29 | A raster function template. 30 | 31 | Aggregate 32 | Adapter function for raster functions written in python. 33 | F32 34 | 35 | 36 | 37 | PythonModule 38 | Rasters 39 | ClassName 40 | method 41 | 42 | 43 | Aggregate.py 44 | 45 | Raster[] 46 | 47 | 48 | false 49 | 50 | Aggregate 51 | Average 52 | 53 | 54 | 55 | Sum 56 | 57 | 58 | 59 | 2 60 | 61 | 62 | GroupName 63 | Tag 64 | -------------------------------------------------------------------------------- /functions/deprecated/FocalStatistics.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | FocalStatistics 3 | A raster function template. 4 | 5 | Focal Statistics 6 | A raster function written in Python. 7 | UNKNOWN 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | factor 14 | raster 15 | 16 | 17 | FocalStatistics.py 18 | FocalStatistics 19 | 5 20 | 21 | Raster 22 | 23 | 24 | true 25 | 26 | 27 | 28 | 29 | 0 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /functions/deprecated/HeatIndex.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | HeatIndex 4 | A raster function template that combines ambient air temperature and relative humidity to return apparent temperature. 5 | 6 | HeatIndex 7 | 8 | F32 9 | 10 | 11 | 12 | PythonModule 13 | ClassName 14 | temperature 15 | rh 16 | units 17 | outunits 18 | 19 | 20 | HeatIndex.py 21 | HeatIndex 22 | 23 | Temperature 24 | A single-band raster where pixel values represent ambient air temperature. 25 | 26 | true 27 | 28 | Raster1 29 | t@SFC 30 | 31 | 32 | 33 | RH 34 | A single-band raster where pixel values represent relative humidity as a percentage value between 0 and 100. 35 | 36 | true 37 | 38 | Raster2 39 | rh@SFC 40 | 41 | 42 | 43 | Temperature-Units 44 | The unit of measurement associated with the input temperature raster. 45 | Fahrenheit 46 | false 47 | 48 | 49 | Output-Units 50 | The unit of measurement associated with the output heat-index raster. 51 | Fahrenheit 52 | false 53 | 54 | 55 | 56 | 57 | 2 58 | 59 | 60 | GroupName 61 | Tag 62 | -------------------------------------------------------------------------------- /functions/deprecated/Hillshade-ScaleAdjusted-Py.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | Scale-Adjusted Hillshade 3 | A raster function template. 4 | 5 | Hillshade Function 6 | 7 | U8 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | raster 14 | zf 15 | ce 16 | cf 17 | 18 | 19 | Hillshade.py 20 | Hillshade 21 | 22 | Raster 23 | 24 | 25 | true 26 | 27 | 28 | zf 29 | 30 | 1 31 | false 32 | 33 | 34 | ce 35 | 36 | 0.66400000000000003 37 | false 38 | 39 | 40 | cf 41 | 42 | 0.024 43 | false 44 | 45 | 46 | 47 | 48 | 0 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /functions/deprecated/KeyMetadata.py: -------------------------------------------------------------------------------- 1 | from utils import loadJSON 2 | 3 | 4 | class KeyMetadata(): 5 | 6 | def __init__(self): 7 | self.name = "Key Metadata Function" 8 | self.description = "Override or insert key-metadata of a raster in a function chain." 9 | self.datasetProps = {} 10 | self.bandProps = [] 11 | 12 | def getParameterInfo(self): 13 | return [ 14 | { 15 | 'name': 'raster', 16 | 'dataType': 'raster', 17 | 'value': None, 18 | 'displayName': "Raster", 19 | 'required': True, 20 | 'description': "The primary raster input." 21 | }, 22 | { 23 | 'name': 'property', 24 | 'dataType': 'string', 25 | 'value': '', 26 | 'displayName': "Property Name", 27 | 'required': False, 28 | 'description': "The name of the optional dataset-level key property to override." 29 | }, 30 | { 31 | 'name': 'value', 32 | 'dataType': 'string', 33 | 'value': None, 34 | 'displayName': "Property Value", 35 | 'required': False, 36 | 'description': "The overriding new value of the dataset-level key property." 37 | }, 38 | { 39 | 'name': 'bands', 40 | 'dataType': 'string', 41 | 'value': '', 42 | 'displayName': "Band Names", 43 | 'required': False, 44 | 'description': "A comma-separated string representing updated band names." 45 | }, 46 | { 47 | 'name': 'json', 48 | 'dataType': 'string', 49 | 'value': '', 50 | 'displayName': "Metadata JSON", 51 | 'required': False, 52 | 'description': ("Key metadata to be injected into the outgoing raster described as a " 53 | "JSON string representing a collection of key-value pairs. " 54 | "Learn more by searching for 'Raster Key Properties' at http://resources.arcgis.com.") 55 | }, 56 | ] 57 | 58 | def getConfiguration(self, **scalars): 59 | return { 60 | 'invalidateProperties': 8, # reset any key properties held by the parent function raster dataset 61 | } 62 | 63 | def updateRasterInfo(self, **kwargs): 64 | try: 65 | jsonInput = kwargs.get('json', "{}").strip() 66 | allProps = loadJSON(jsonInput) if jsonInput else {} 67 | except ValueError as e: 68 | raise Exception(e.message) 69 | 70 | self.datasetProps = { k.lower(): v for k, v in allProps.items() if k != 'bandproperties' } 71 | 72 | # inject name-value pair into bag of properties 73 | p = kwargs.get('property', "").lower() 74 | if p: self.datasetProps[p] = kwargs.get('value', None) 75 | 76 | # get bandproperties array from original JSON as a list of dictionaries... 77 | self.bandProps = [] 78 | for d in allProps.get('bandproperties', []): 79 | self.bandProps.append( 80 | { k.lower(): v for k, v in d.items() } if isinstance(d, dict) else None) 81 | 82 | # ensure size of bandProps matches input band count 83 | bandCount = kwargs['raster_info']['bandCount'] 84 | self.bandProps.extend([{} for k in range(0, bandCount-len(self.bandProps))]) 85 | 86 | # inject band names into the bandProps dictionary 87 | bands = kwargs.get('bands', "").strip() 88 | if bands: 89 | bandNames = bands.split(',') 90 | for k in range(0, min(len(self.bandProps), len(bandNames))): 91 | b = bandNames[k].strip() 92 | if b: self.bandProps[k]['bandname'] = b 93 | 94 | return kwargs 95 | 96 | def updateKeyMetadata(self, names, bandIndex, **keyMetadata): 97 | # return keyMetadata dictionary with updated values for entries in [names]... 98 | properties = self.datasetProps if bandIndex == -1 else self.bandProps[bandIndex] 99 | if not properties: 100 | return keyMetadata 101 | 102 | skipCheck = not bool(names) # => key names are internally generated, not user-specified 103 | for k in (names or properties): # iterate over either of those containers 104 | if skipCheck or k in properties: # spend time checking for existence only if necessary 105 | v = properties[k] 106 | keyMetadata[str(k)] = str(v) if isinstance(v, unicode) else v 107 | 108 | return keyMetadata 109 | -------------------------------------------------------------------------------- /functions/deprecated/LinearSpectralUnmixing.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | Linear Spectral Unmixing 3 | Performs linear spectral unmixing for a multiband raster. 4 | 5 | Extract Band Function 6 | Reorders or extracts bands from a raster. 7 | UNKNOWN 8 | 9 | 10 | 11 | Raster 12 | BandIDs 13 | MissingBandAction 14 | 15 | 16 | 17 | Raster_2015228_53921_488 18 | 19 | 20 | Raster Function Template 21 | A raster function template. 22 | 23 | Linear Spectral Unmixing 24 | Performs linear spectral unmixing for a multiband raster. 25 | UNKNOWN 26 | 27 | 28 | 29 | raster 30 | signatures 31 | method 32 | PythonModule 33 | ClassName 34 | 35 | 36 | 37 | Raster 38 | 39 | 40 | true 41 | 42 | 43 | Signatures 44 | 45 | {"Shadow": [70.05629, 27.24081, 25.31275, 24.17432, 31.77904, 17.82422], "Veg": [65.46086, 30.09995, 26.27376, 117.45741, 76.96012, 26.25062], "NPV": [74.74029, 32.06931, 35.57350, 32.66032, 73.63062, 60.51104], "Soil": [143.65580, 79.30271, 102.82176, 93.60246, 176.57705, 117.49280]} 46 | false 47 | 48 | 49 | Method 50 | 51 | Scaled 52 | false 53 | 54 | LinearSpectralUnmixing.py 55 | LinearSpectralUnmixing 56 | 57 | 58 | 59 | 0 60 | 61 | 62 | 63 | 64 | 65 | true 66 | 67 | 68 | BandIDs_2015228_53921_488 69 | 70 | 71 | 0 72 | 73 | false 74 | 75 | 76 | MissingBandAction_2015228_53921_488 77 | 78 | 0 79 | false 80 | 81 | 82 | 83 | 84 | 0 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /functions/deprecated/MergeRasters.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | MergeRasters 3 | Merges a collection of rasters on the fly. 4 | 5 | Merge Rasters Function 6 | Merges a collection of rasters on the fly. 7 | UNKNOWN 8 | 9 | 10 | 11 | Rasters 12 | 13 | 14 | 15 | Raster[] 16 | 17 | 18 | false 19 | 20 | 21 | 22 | 23 | 0 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /functions/deprecated/MultidirectionalHillshade.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Multidirectional Hillshade 4 | A raster function template. 5 | 6 | Multidirectional Hillshade 7 | Adapter function for raster functions written in python. 8 | F32 9 | 10 | 11 | 12 | PythonModule 13 | raster 14 | 15 | 16 | MultidirectionalHillshade.pyd 17 | 18 | Raster 19 | 20 | 21 | true 22 | 23 | 24 | 25 | 26 | 0 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /functions/deprecated/NDVI-Colormap.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | NDVI-Colormap 4 | A raster function template. 5 | 6 | NDVI 7 | Adapter function for raster functions written in python. 8 | F32 9 | 10 | 11 | 12 | PythonModule 13 | raster 14 | red 15 | ir 16 | method 17 | 18 | 19 | NDVI.py 20 | 21 | Raster 22 | 23 | 24 | true 25 | 26 | 3 27 | 4 28 | Colormap 29 | 30 | 31 | 32 | 0 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /functions/deprecated/NDVI-Grayscale.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | NDVI-Grayscale 4 | A raster function template. 5 | 6 | NDVI 7 | Adapter function for raster functions written in python. 8 | F32 9 | 10 | 11 | 12 | PythonModule 13 | raster 14 | red 15 | ir 16 | method 17 | 18 | 19 | NDVI.py 20 | 21 | Raster 22 | 23 | 24 | true 25 | 26 | 3 27 | 4 28 | Grayscale 29 | 30 | 31 | 32 | 0 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /functions/deprecated/NDVI.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | NDVI-Raw 4 | A raster function template. 5 | 6 | NDVI 7 | Adapter function for raster functions written in python. 8 | F32 9 | 10 | 11 | 12 | PythonModule 13 | raster 14 | red 15 | ir 16 | method 17 | 18 | 19 | NDVI.py 20 | 21 | Raster 22 | 23 | 24 | true 25 | 26 | 3 27 | 4 28 | Raw 29 | 30 | 31 | 32 | 0 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /functions/deprecated/Random.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class Random(): 5 | 6 | def __init__(self): 7 | self.name = "Random Raster Function" 8 | self.description = "" 9 | 10 | 11 | def getParameterInfo(self): 12 | return [] 13 | 14 | 15 | def getConfiguration(self, **scalars): 16 | return { 17 | 'inheritProperties': 0, # no input raster, nothing to inherit. 18 | 'invalidateProperties': 1 | 2 | 4 | 8, # reset everything on the parent dataset. 19 | 'resampling': True, 20 | } 21 | 22 | 23 | def updateRasterInfo(self, **kwargs): 24 | nBands = 3 25 | minX, minY, maxX, maxY = 0.0, 0.0, 1000.0, 1000.0 26 | dX, dY = 10.0, 10.0 27 | sr = 3857 28 | 29 | outputInfo = { 30 | 'bandCount': nBands, 31 | 'pixelType': 'u1', 32 | 'nativeExtent': (minX, minY, maxX, maxY), 33 | 'extent': (minX, minY, maxX, maxY), 34 | 'cellSize': (dX, dY), 35 | 'spatialReference': sr, 36 | 'nativeSpatialReference': sr, 37 | 'statistics': (), 38 | 'histogram': (), 39 | 'colormap': (), 40 | 'noData': np.array([256], dtype='u1'), 41 | } 42 | 43 | kwargs['output_info'] = outputInfo 44 | return kwargs 45 | 46 | 47 | def updatePixels(self, tlc, shape, props, **pixelBlocks): 48 | outBlock = 255.0 * np.random.random_sample(shape) 49 | pixelBlocks['output_pixels'] = outBlock.astype(props['pixelType']) 50 | pixelBlocks['output_mask'] = np.ones(shape).astype('u1') 51 | return pixelBlocks 52 | -------------------------------------------------------------------------------- /functions/deprecated/Random.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Random 4 | A raster function template. 5 | 6 | Random 7 | Adapter function for raster functions written in python. 8 | 9 | 10 | 11 | 12 | PythonModule 13 | 14 | 15 | Random.py 16 | 17 | 18 | 19 | 0 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /functions/deprecated/Subtract.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | Subtract 3 | A raster function template that returns the difference of two rasters. 4 | 5 | Arithmetic Function 6 | Performs an arithmetic operation between two partially or completely spatially overlapping rasters or a raster and one or more constant values. 7 | UNKNOWN 8 | 9 | 10 | 11 | Raster 12 | Raster2 13 | Operation 14 | ExtentType 15 | CellsizeType 16 | RasterToGenerate 17 | 18 | 19 | 20 | Raster1 21 | 22 | 23 | true 24 | 25 | 26 | Raster2 27 | 28 | 29 | true 30 | 31 | 32 | Operation 33 | 34 | 2 35 | false 36 | 37 | 38 | ExtentType 39 | 40 | 1 41 | false 42 | 43 | 44 | CellsizeType 45 | 46 | 1 47 | false 48 | 49 | 50 | RasterToGenerate 51 | 52 | 0 53 | false 54 | 55 | 56 | 57 | 58 | 2 59 | 60 | 61 | GroupName 62 | Tag 63 | 64 | -------------------------------------------------------------------------------- /functions/deprecated/Windchill.rft.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Windchill 4 | A raster function template that computes windchill given rasters representing wind-speed and temperature. 5 | 6 | Windchill 7 | 8 | F32 9 | 10 | 11 | 12 | PythonModule 13 | ClassName 14 | temperature 15 | ws 16 | tunits 17 | wunits 18 | ounits 19 | 20 | 21 | Windchill.py 22 | Windchill 23 | 24 | Temperature 25 | 26 | 27 | true 28 | 29 | Raster1 30 | t@SFC 31 | 32 | 33 | 34 | WS 35 | 36 | 37 | true 38 | 39 | Raster2 40 | Windspeed 41 | windspd@SFC 42 | 43 | 44 | 45 | Temperature-Units 46 | 47 | Fahrenheit 48 | false 49 | 50 | 51 | Windspeed-Units 52 | 53 | mph 54 | false 55 | 56 | 57 | Output-Units 58 | 59 | Fahrenheit 60 | false 61 | 62 | 63 | 64 | 65 | 2 66 | 67 | 68 | GroupName 69 | Variable 70 | -------------------------------------------------------------------------------- /functions/deprecated/ZonalRemap.rft.xml: -------------------------------------------------------------------------------- 1 | 2 | ZonalRemap 3 | 4 | 5 | Zonal Remap 6 | A raster function written in Python. 7 | UNKNOWN 8 | 9 | 10 | 11 | PythonModule 12 | ClassName 13 | vraster 14 | zraster 15 | ztable 16 | zid 17 | zmin 18 | zmax 19 | zval 20 | background 21 | defzval 22 | where 23 | 24 | 25 | ZonalRemap.py 26 | ZonalRemap 27 | 28 | Raster 29 | 30 | 31 | 32 | Raster1 33 | 34 | true 35 | 36 | 37 | ZoneRaster 38 | 39 | 40 | 41 | Raster2 42 | 43 | true 44 | 45 | 46 | ztable 47 | 48 | 49 | false 50 | 51 | 52 | zid 53 | 54 | ZoneID 55 | false 56 | 57 | 58 | zmin 59 | 60 | ZoneMin 61 | false 62 | 63 | 64 | zmax 65 | 66 | ZoneMax 67 | false 68 | 69 | 70 | zval 71 | 72 | ZoneValue 73 | false 74 | 75 | 76 | background 77 | 78 | 0 79 | false 80 | 81 | 82 | defzval 83 | 84 | 255 85 | false 86 | 87 | 88 | where 89 | 90 | 91 | false 92 | 93 | 94 | 95 | 96 | 2 97 | 98 | 99 | GroupName 100 | Tag 101 | 102 | 103 | -------------------------------------------------------------------------------- /functions/functions.pyproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | 2.0 6 | {4842f09b-120b-4468-ae7c-f48cb0adf7bb} 7 | 8 | KeyMetadata.py 9 | 10 | . 11 | . 12 | {888888a0-9f3d-457c-b088-3a5042f75d52} 13 | Standard Python launcher 14 | 15 | 16 | 17 | 18 | 19 | 20 | 10.0 21 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /raster-functions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "functions", "functions\functions.pyproj", "{4842F09B-120B-4468-AE7C-F48CB0ADF7BB}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {4842F09B-120B-4468-AE7C-F48CB0ADF7BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {4842F09B-120B-4468-AE7C-F48CB0ADF7BB}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /scripts/ExtractRasterInfo.py: -------------------------------------------------------------------------------- 1 | import arcpy 2 | import arcpy.sa as sa 3 | from glob import glob 4 | 5 | print("ObjectID,Raster,NCols,NRows,NBands,PixelType,XMin,YMin,XMax,YMax,SRS") 6 | 7 | for k, f in enumerate(glob(r"e:\raster-types\Data\Tiles\*.tif"), 1): 8 | r = sa.Raster(f) 9 | e = r.extent 10 | print(",".join((str(k), str(f), str(r.width), str(r.height), str(r.bandCount), str(r.pixelType), str(e.XMin), str(e.YMin), str(e.XMax), str(e.YMax), str(r.spatialReference.factoryCode)))) 11 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | pyparsing 2 | pyproj 3 | python-dateutil 4 | pytz 5 | six 6 | numpy 7 | scipy 8 | matplotlib 9 | scikit-image 10 | scikit-learn 11 | Cython -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ==================================================================================== 3 | setup.py: Automated installation of dependencies required for Python raster function 4 | ==================================================================================== 5 | 6 | Installation 7 | ------------ 8 | 9 | To execute **setup.py** within the package directory run: 10 | $ python setup.py 11 | ''' 12 | 13 | import sys 14 | import logging 15 | from os import path, makedirs 16 | from subprocess import call 17 | from time import sleep 18 | 19 | 20 | ''' 21 | ErrorLevel on exit: 22 | 0 : Installation successful. 23 | 1 : PIP installation unsuccessful. 24 | 2 : File cannot be downloaded. 25 | 4 : VC++ Compiler for Python installation failed. 26 | 5 : Requirements.txt file not found. 27 | 6 : Python package installation failed. 28 | 99 : ArcGIS 10.3.1 or above not found. 29 | ''' 30 | 31 | def log(s): 32 | print(">>> {0}".format(s)) 33 | 34 | 35 | def die(errorLog, errorCode): 36 | print("\n\n") 37 | logging.error(errorLog) 38 | print("\n") 39 | sleep(2) 40 | exit(errorCode) 41 | 42 | 43 | def downloadFile(url, filePath): 44 | try: 45 | log("Downloading: {0} to {1}".format(url, filePath)) 46 | 47 | try: 48 | from urllib2 import urlopen 49 | except: 50 | from urllib.request import urlopen 51 | 52 | d = path.dirname(filePath) 53 | if not path.exists(d): 54 | makedirs(d) 55 | 56 | with open(filePath, 'wb') as f: 57 | f.write(urlopen(url).read()) 58 | except: 59 | die("Unable to download URL", 2) 60 | 61 | 62 | def locateFile(url, filePath): 63 | if not path.isfile(filePath): 64 | downloadFile(url, filePath) 65 | log("Located: {0}".format(filePath)) 66 | 67 | 68 | def main(): 69 | pipURL = "http://bootstrap.pypa.io/get-pip.py" 70 | vcURL = "http://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi" 71 | 72 | pipExePath = path.join(path.dirname(sys.executable), r"Scripts\pip.exe") 73 | setupHome = path.join(path.abspath(path.dirname(__file__)), "scripts") 74 | distHome = path.join(path.abspath(path.dirname(__file__)), "dist") 75 | 76 | try: 77 | log("Installing PIP") 78 | pipPyPath = path.join(setupHome, "get-pip.py") 79 | locateFile(pipURL, pipPyPath) 80 | call([sys.executable, pipPyPath]) 81 | 82 | if path.isfile(pipExePath): 83 | log("PIP installed successfully") 84 | else: 85 | raise Exception("PIP failed") 86 | 87 | call([pipExePath, "install", "--upgrade", "--no-index", "--find-links={0}".format(distHome), "pip"]) 88 | call([pipExePath, "install", "--upgrade", "--no-index", "--find-links={0}".format(distHome), "wheel"]) 89 | except: 90 | die("PIP installation failed!", 1) 91 | 92 | try: 93 | if sys.version_info[0] == 2: 94 | log("Installing Microsoft Visual C++ Compiler") 95 | vcSetupPath = path.join(distHome, "VCForPython27.msi") 96 | locateFile(vcURL, vcSetupPath) 97 | c = ["msiexec", "/i", vcSetupPath, "/qb-"] 98 | log("Executing: {0}".format(" ".join(c))) 99 | call(c) 100 | log("C++ Compiler for Python installed successfully") 101 | except: 102 | die("VC++ Compiler for Python installation failed!.", 4) 103 | 104 | try: 105 | log("Installing Python dependencies") 106 | reqFilePath = path.join(setupHome, "requirements.txt") 107 | if not path.isfile(reqFilePath): 108 | die("Dependency listing file not found: {0}".format(reqFilePath), 5) 109 | 110 | c = [pipExePath, "install", "--upgrade", "--no-index", "--find-links={0}".format(distHome), "-r", reqFilePath] 111 | log("Executing: {0}".format(" ".join(c))) 112 | call(c) 113 | except: 114 | die("Dependency installation failed!", 6) 115 | 116 | try: 117 | arcpy = __import__('arcpy') 118 | info = arcpy.GetInstallInfo() 119 | 120 | bVersionOK = True 121 | 122 | minArcGISVersion = '10.3.1' 123 | if info['Version'].split(".")[0] == 10 and (tuple(map(int, (info['Version'].split(".")))) < tuple(map(int, (minArcGISVersion.split("."))))): 124 | bVersionOK = False 125 | 126 | minProVersion = '1.0' 127 | if info['Version'].split(".")[0] == 1 and (tuple(map(int, (info['Version'].split(".")))) < tuple(map(int, (minProVersion.split("."))))): 128 | bVersionOK = False 129 | 130 | if not bVersionOK: 131 | raise Exception("No ArcGIS") 132 | 133 | print("\n\n") 134 | if info['Version'][0] == 10: 135 | log("Python extensions for raster functions in ArcGIS {} {} build {} successfully installed.".format( 136 | info['ProductName'], info['Version'], info['BuildNumber'])) 137 | else: 138 | log("Python extensions for raster functions in {} {} build {} successfully installed.".format( 139 | info['ProductName'], info['Version'], info['BuildNumber'])) 140 | except: 141 | logging.warn("Unable to find ArcGIS 10.3.1/ArcGIS Pro 1.0 or above.") 142 | 143 | log("Done.") 144 | sleep(2) 145 | exit(0) 146 | 147 | 148 | if __name__ == '__main__': 149 | main() 150 | 151 | 152 | # Uninstall using: pip uninstall --yes -r requirements.txt --------------------------------------------------------------------------------