I recently needed/wanted to write a filter function to use with GDAL in Python. What I wanted was something that could be passed a kernel such as spatial average (blur) or edge detection (actually it was this I was after mostly). So this is the code I wrote for it.
Lets start with the selection of a kernel (filter). Firstly, if I know which one I want I can pass the name to the selection function, otherwise the function sets the choice to nothing to begin with.
Then there is a dictionary containing all the kernels as Numpy arrays (see code below). After which the function asks for the choice if none has been passed to it already and returns the array and its name. Note also that Numpy has been imported as np (
Lets start with the selection of a kernel (filter). Firstly, if I know which one I want I can pass the name to the selection function, otherwise the function sets the choice to nothing to begin with.
def kernelChoice(ans = ''):
Then there is a dictionary containing all the kernels as Numpy arrays (see code below). After which the function asks for the choice if none has been passed to it already and returns the array and its name. Note also that Numpy has been imported as np (
import numpy as np
). def kernelChoice(ans = ''):
'''Returns a kernel to use with mustard as well as the name of the kernel.
To use: kernel, ans = kernelChoice(ans = '') '''
# Create dictionary of kernels
kernelDict ={
'Average':np.array([[0.111,0.111,0.111],[0.111,0.111,0.111],[0.111,0.111,0.111]]),
'HighPass1':np.array([[-0.7,-1.0,-0.7],[-1.0,6.8,-1.0],[-0.7,-1.0,-0.7]]),
'HighPass2':np.array([[-1.0,-1.0,-1.0],[-1.0,9.0,-1.0],[-1.0,-1.0,-1.0]]),
'PrewittX':np.array([[-1.0,0.0,1.0],[-1.0,0.0,1.0],[-1.0,0.0,1.0]]),
'PrewittY':np.array([[1.0,1.0,1.0],[0.0,0.0,0.0],[-1.0,-1.0,-1.0]]),
'SobelX':np.array([[-1.0,0.0,1.0],[-2.0,0.0,2.0],[-1.0,0.0,1.0]]),
'SobelY':np.array([[1.0,2.0,1.0],[0.0,0.0,0.0],[-1.0,-2.0,-1.0]])
}
# Create list of kernel names
KerList = kernelDict.keys()
KerList.sort()
# Get choice from user
while ans not in KerList:
print KerList
ans = raw_input('Enter name of kernel: ')
kernel = kernelDict[ans]
print ans,':\n', kernel
return kernel, ans
The next step then is to open the raster file, read in the data and then run the kernel over that. This function calls upon rasterImport which we will also define as function further down, but is similar to previous raster importers I have messed around with before. Getting the code for that sorted out should be a priority because as it stands now it is pretty messy and requires a lot of manipulation. Well, back to this code.
The mustard function sets NoData values in the imported data grid to Numpy nan. Then there is a whole load of getting shapes and sizes in order. After which an array, based on the kernel is created that contains relative locations to the central pixel; left and up are negative, right and down are positive (remember that coordinates here start at the upper left).
Then we loop starting top left going right then down to the next row, left to right each time. Then at last the filtered raster is returned.
The mustard function sets NoData values in the imported data grid to Numpy nan. Then there is a whole load of getting shapes and sizes in order. After which an array, based on the kernel is created that contains relative locations to the central pixel; left and up are negative, right and down are positive (remember that coordinates here start at the upper left).
Then we loop starting top left going right then down to the next row, left to right each time. Then at last the filtered raster is returned.
def mustard(file,kernel):
'''Run a kernel (window,matrix) over a single layer raster. Uses rasterImport and rasterExport.
Kernel must be numpy array of odd number dimensions.
To use: outdata = mustard(file,kernel) '''
# Import data file
data, meta, metadata = rasterImport(file)
# Set no data values to numpy nan
data[data==meta['dats'][0]]=np.nan
# Get size of indata array
inrows = meta['dimension']['rows']
incols = meta['dimension']['cols']
# Get size of kernel
krows = np.shape(kernel)[0]
kcols = np.shape(kernel)[1]
# Check kernel is smaller than data grid.
if krows >= inrows or kcols >= incols: sys.exit('Bad kernel. Too large.')
# Check kernel has a central pixel
if krows % 2 == 0: sys.exit('Bad kernel. Even number rows.')
if kcols % 2 == 0: sys.exit('Bad kernel. Even number columns.')
# Get central pixel location in kernel
kmidrow = int(krows)/2
kmidcol = int(kcols)/2
# Create relative extent of kernel
rowminext = -1*((krows-1)/2)
rowmaxext = (krows-1)/2
rowrange = range(rowminext,rowmaxext+1,1)
colminext = -1*((kcols-1)/2)
colmaxext = (kcols-1)/2
colrange = range(colminext,colmaxext+1,1)
# Set initial starting location of kernel on grid
dati = kmidrow
datj = kmidcol
# Get number of rows to run kernel over
gridrows = range(inrows + rowminext*2)
# Get number of columns to run kernel over
gridcols = range(incols + colminext*2)
# Create output array filled with nan
outdata = np.empty((inrows,incols))
outdata[:] = np.nan
# Start loop
for row in gridrows:
datj = kmidcol
rowvec = np.ones((1,krows))*dati + rowrange
for col in gridcols:
if np.isnan(data[dati,datj]):
datj = datj + 1
outdata[dati,datj] = np.nan
else:
colvec = np.ones((1,kcols))*datj + colrange
extract = np.empty((krows,kcols))
for i in range(krows):
for j in range(kcols):
extract[i,j] = data[int(rowvec[0,i]),int(colvec[0,j])]
pixval = np.nansum(extract * kernel)
outdata[dati,datj] = pixval
datj = datj + 1
dati = dati + 1
return outdata
To finish off with here are the other functions that make this work as well as a few lines to get everything running. Don’t forget to import Numpy as well as os to get this all working.
def namer(pathstring):
'''Get name and extension of a file
To use: name,ext,path,namefull = namer(pathstring) '''
## Called by: kriging
namefull = os.path.basename(pathstring)
path = os.path.dirname(pathstring)
ext = namefull.split('.')[1]
name = namefull.split('.')[0]
return name,ext,path,namefull
#
def rasterImport(file):
'''Import a raster file and return grid plus meta data.
To use: data, meta, metadata = rasterImport(file)'''
time_one = datetime.now()
print '\nImporting ',file
#print time_one.strftime('at day:%j %H:%M:%S')
# register all of the GDAL drivers
gdal.AllRegister()
# Open file
raster = gdal.Open(file)
if raster is None:
print 'Could not open ',file,'\n'
sys.exit(1)
# Get coordinate system parameters
projec = raster.GetProjection()
srs=osr.SpatialReference(wkt=projec)
transf = raster.GetGeoTransform()
ul_x = transf[0]
ul_y = transf[3]
xres = transf[1]
yres = transf[5]
# get image size
rows = raster.RasterYSize
cols = raster.RasterXSize
dims = {'xres':xres,'yres':yres,'rows':rows,'cols':cols}
# Calculate corners
ll_x = ul_x
ll_y = ul_y + (rows * yres)
ur_x = ul_x + (cols * xres)
ur_y = ul_y
lr_x = ur_x
lr_y = ll_y
corners = {'ll':(ll_x,ll_y),'ul':(ul_x,ul_y),'ur':(ur_x,ur_y),'lr':(lr_x,lr_y)}
#
driveshrt = raster.GetDriver().ShortName
driver = gdal.GetDriverByName(driveshrt)
metadata = driver.GetMetadata()
metakeys = metadata.keys()
# Read the file band to a matrix called band_1
band = raster.GetRasterBand(1)
# Access data in rastern band as array
data = band.ReadAsArray(0,0,cols,rows)
#
# gdal interpolation creates "upside down files, hence this
Yres=yres
if Yres/np.fabs(Yres)==-1:
Yres=-1*Yres
data = np.flipud(data)
#
# get nodata value
nandat = band.GetNoDataValue()
# get minimum and maximum value
mindat = band.GetMinimum()
maxdat = band.GetMaximum()
if mindat is None or maxdat is None:
(mindat,maxdat) = band.ComputeRasterMinMax(1)
dats = [nandat,mindat,maxdat]
sumvals = data[np.where(np.logical_not(data == nandat))]
sumval = sumvals.sum()
numvals = len(sumvals)
avval = sumval/numvals
volvals = sumval*(math.fabs((transf[1]*transf[5])))
meta = {'transform':transf,'projection':projec,'corners':corners,'dimension':dims,'dats':dats,'sumval':sumval,'numvals':numvals,'avval':avval,'volvals':volvals}
if srs.IsProjected:
print 'projcs 1: ',srs.GetAttrValue('projcs')
print 'projcs 2: ',srs.GetAuthorityCode('projcs')
print 'projcs 3: ',srs.GetAuthorityName('projcs')
print 'geogcs 1:: ',srs.GetAttrValue('geogcs')
print 'Sum of pixel values = ',sumval,' in range ',mindat,' to ',maxdat
print 'From ',numvals,' of ',rows,' X ',cols,' = ',rows*cols,' pixels, or ',((1.0*numvals)/(rows*cols))*100,'%'
print 'Average then is ',avval
print 'Pixels size, x:',transf[1],' y:',transf[5]
print '"Volume" is then the sum * 1 pixel area: ',volvals
return data, meta, metadata
#
def rasterExport(outdata,meta,filnm):
'''Export array as single layer GeoTiff. Uses metadata from rasterImport and numpy nan as mask.
To use: rasterExport(outdata,meta,filnm)'''
inrows = meta['dimension']['rows']
incols = meta['dimension']['cols']
datdriver = gdal.GetDriverByName( "GTiff" )
datout = datdriver.Create(filnm,incols,inrows,1,gdal.GDT_Float32)
datout.SetGeoTransform(meta['transform'])
datout.SetProjection(meta['projection'])
outdata_m = np.flipud(outdata)
outdata_m = np.where(np.isnan(outdata_m),meta['dats'][0],outdata_m)
datout.GetRasterBand(1).WriteArray(outdata_m)
datout.GetRasterBand(1).SetNoDataValue(meta['dats'][0])
datout.GetRasterBand(1).ComputeStatistics(True)
datout = None
return 0
#
# Here is a little starter, just set the variable 'file' first
file = './file1.tif'
name,ext,path,namefull = AM.namer(file)
filnm = os.path.join(path,(name + ans + '.tif'))
outdata,meta = mustard(file,kernel)
rasterExport(outdata,meta,filnm)
No comments:
Post a Comment