¿Interpolación bilineal de datos de puntos en un ráster en Python?

12

Tengo una trama con la que me gustaría hacer algunas interpolaciones de puntos. Aquí es donde estoy:

from osgeo import gdal
from numpy import array

# Read raster
source = gdal.Open('my_raster.tif')
nx, ny = source.RasterXSize, source.RasterYSize
gt = source.GetGeoTransform()
band_array = source.GetRasterBand(1).ReadAsArray()
# Close raster
source = None

# Compute mid-point grid spacings
ax = array([gt[0] + ix*gt[1] + gt[1]/2.0 for ix in range(nx)])
ay = array([gt[3] + iy*gt[5] + gt[5]/2.0 for iy in range(ny)])

Hasta ahora, he probado la función interp2d de SciPy :

from scipy import interpolate
bilinterp = interpolate.interp2d(ax, ay, band_array, kind='linear')

Sin embargo, aparece un error de memoria en mi sistema Windows de 32 bits con un ráster 317 × 301:

Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "C:\Python25\Lib\site-packages\scipy\interpolate\interpolate.py", line 125, in __init__
    self.tck = fitpack.bisplrep(self.x, self.y, self.z, kx=kx, ky=ky, s=0.)
  File "C:\Python25\Lib\site-packages\scipy\interpolate\fitpack.py", line 873, in bisplrep
tx,ty,nxest,nyest,wrk,lwrk1,lwrk2)
MemoryError

Admito que tengo una confianza limitada en esta función SciPy, ya que los parámetros bounds_erroro fill_valueno funcionan según lo documentado. No veo por qué debería tener un error de memoria, ya que mi raster es 317 × 301, y el algoritmo bilineal no debería ser difícil.

¿Alguien ha encontrado un buen algoritmo de interpolación bilineal, preferiblemente en Python, posiblemente diseñado con NumPy? ¿Alguna pista o consejo?


(Nota: el algoritmo de interpolación vecino más cercano es fácil:

from numpy import argmin, NAN

def nearest_neighbor(px, py, no_data=NAN):
    '''Nearest Neighbor point at (px, py) on band_array
    example: nearest_neighbor(2790501.920, 6338905.159)'''
    ix = int(round((px - (gt[0] + gt[1]/2.0))/gt[1]))
    iy = int(round((py - (gt[3] + gt[5]/2.0))/gt[5]))
    if (ix < 0) or (iy < 0) or (ix > nx - 1) or (iy > ny - 1):
        return no_data
    else:
        return band_array[iy, ix]

... pero prefiero los métodos de interpolación bilineal)

Mike T
fuente
1
¿Tal vez obtienes el MemoryErrorporque NumPy intenta acceder más allá de tu band_array? Debe verificar axy ay.
olt
1
ax, ay podría tener un problema si la cuadrícula está rotada Podría ser mejor transformar sus puntos de interpolación en píxeles o coordenadas de datos. Además, si hay un problema individual con ellos, entonces es posible que esté yendo más allá del tamaño de la banda.
Dave X
Las cuadrículas correctas y rotadas requieren transformación al espacio de cuadrícula y luego de regreso al espacio de coordenadas. Esto requiere la inversa de los coeficientes de transformación afines en gt.
Mike T

Respuestas:

7

Traduje la fórmula a continuación (de Wikipedia ) a Python-speak para obtener el siguiente algoritmo, que parece funcionar.

from numpy import floor, NAN

def bilinear(px, py, no_data=NAN):
    '''Bilinear interpolated point at (px, py) on band_array
    example: bilinear(2790501.920, 6338905.159)'''
    ny, nx = band_array.shape
    # Half raster cell widths
    hx = gt[1]/2.0
    hy = gt[5]/2.0
    # Calculate raster lower bound indices from point
    fx = (px - (gt[0] + hx))/gt[1]
    fy = (py - (gt[3] + hy))/gt[5]
    ix1 = int(floor(fx))
    iy1 = int(floor(fy))
    # Special case where point is on upper bounds
    if fx == float(nx - 1):
        ix1 -= 1
    if fy == float(ny - 1):
        iy1 -= 1
    # Upper bound indices on raster
    ix2 = ix1 + 1
    iy2 = iy1 + 1
    # Test array bounds to ensure point is within raster midpoints
    if (ix1 < 0) or (iy1 < 0) or (ix2 > nx - 1) or (iy2 > ny - 1):
        return no_data
    # Calculate differences from point to bounding raster midpoints
    dx1 = px - (gt[0] + ix1*gt[1] + hx)
    dy1 = py - (gt[3] + iy1*gt[5] + hy)
    dx2 = (gt[0] + ix2*gt[1] + hx) - px
    dy2 = (gt[3] + iy2*gt[5] + hy) - py
    # Use the differences to weigh the four raster values
    div = gt[1]*gt[5]
    return (band_array[iy1,ix1]*dx2*dy2/div +
            band_array[iy1,ix2]*dx1*dy2/div +
            band_array[iy2,ix1]*dx2*dy1/div +
            band_array[iy2,ix2]*dx1*dy1/div)

Tenga en cuenta que el resultado se devolverá con una precisión aparentemente mayor que los datos de origen, ya que se clasifica al dtype('float64')tipo de datos de NumPy . Puede usar el valor de retorno con .astype(band_array.dtype)para hacer que el tipo de datos de salida sea el mismo que la matriz de entrada.

fórmula de interpolación bilineal

Mike T
fuente
3

Lo probé localmente con resultados similares, pero estoy en una plataforma de 64 bits, por lo que no alcanzó el límite de memoria. Quizás, en lugar de eso, intente interpolar piezas pequeñas de la matriz a la vez, como en este ejemplo .

También podría hacer esto con GDAL, desde la línea de comando sería:

gdalwarp -ts $XSIZE*2 0 -r bilinear input.tif interp.tif

Para hacer la operación equivalente en Python, use ReprojectImage () :

mem_drv = gdal.GetDriverByName('MEM')
dest = mem_drv.Create('', nx, ny, 1)

resample_by = 2
dt = (gt[0], gt[1] * resample_by, gt[2], gt[3], gt[4], gt[5] * resample_by)
dest.setGeoTransform(dt)

resampling_method = gdal.GRA_Bilinear    
res = gdal.ReprojectImage(source, dest, None, None, resampling_method)

# then, write the result to a file of your choice...    
scw
fuente
Mis datos de puntos que me gustaría interpolar no están separados regularmente, por lo que no puedo usar la ReprojectImagetécnica incorporada de GDAL .
Mike T
1

He tenido el problema exacto en el pasado, y nunca lo resolví usando interpolate.interp2d. He tenido éxito usando scipy.ndimage.map_coordinates . Intenta lo siguiente:

scipy.ndimage.map_coordinates (band_array, [ax, ay]], order = 1)

Esto parece dar el mismo resultado que bilineal.

Matthew Snape
fuente
Este me sorprendió un poco, ya que no estoy seguro de cómo se usan las coordenadas ráster de origen (en lugar de usar coordenadas de píxeles). Veo que está "vectorizado" para resolver muchos puntos.
Mike T
De acuerdo, realmente no entiendo scipy. Su solución numpy mucho mejor.
Matthew Snape
0

scipy.interpolate.interp2d () funciona bien con scipy más moderno. Creo que las versiones anteriores asumen cuadrículas irregulares y no aprovechan las cuadrículas regulares. Me sale el mismo error que con scipy. versión = 0.11.0, pero en scipy. versión = 0.14.0, felizmente funciona en algunos resultados del modelo 1600x1600.

Gracias por las sugerencias en su pregunta.

#!/usr/bin/env python

from osgeo import gdal
from numpy import array
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("filename",help='raster file from which to interpolate a (1/3,1/3) point from from')
args = parser.parse_args()

# Read raster
source = gdal.Open(args.filename)
nx, ny = source.RasterXSize, source.RasterYSize
gt = source.GetGeoTransform()
band_array = source.GetRasterBand(1).ReadAsArray()
# Close raster
source = None

# Compute mid-point grid spacings
ax = array([gt[0] + ix*gt[1] + gt[1]/2.0 for ix in range(nx)])
ay = array([gt[3] + iy*gt[5] + gt[5]/2.0 for iy in range(ny)])

from scipy import interpolate
bilinterp = interpolate.interp2d(ax, ay, band_array, kind='linear')

x1 = gt[0] + gt[1]*nx/3
y1 = gt[3] + gt[5]*ny/3.

print(nx, ny, x1,y1,bilinterp(x1,y1))

####################################

$ time ./interp2dTesting.py test.tif 
(1600, 1600, -76.322, 30.70889, array([-8609.27777778]))

real    0m4.086s
user    0m0.590s
sys 0m0.252s
Dave X
fuente