Raster reclassify usando python, gdal y numpy

9

Me gustaría reclasificar un archivo ráster de un ráster con 10 clases a un ráster con 8 clases usando pyhton, gdal y / o numpy. Las clases se representan como enteros. He intentado seguir los pasos de esta publicación Reclasificar rásteres usando GDAL y Python , el numpy.equal doc y también gdal_calc doc. Sin embargo, fue en vano.

El archivo ráster que se reclasificará tiene valores enteros que van del 0 al 11 y también incluye los valores 100 y 255. A continuación se muestra la reclasificación (del valor: al valor):

nodata: 4, 0: 4, 1: 1, 2: 2, 3: 3, 4: 3, 5: 4, 6: 5, 7: 5, 8: 6, 9: 7, 10: 8, 100: nodata, 255: nodata,

Lo que he podido hacer es seleccionar el archivo ráster que se reclasificará usando tkinter.FileDialog y obtener la información ráster como la geotransformación y el tamaño de píxel con reclass = gdal.Open (raster, GA_ReadOnly).

¿Cómo hago para resolver lo anterior?

Cabe mencionar que los rásteres a reclasificar pueden ser bastante grandes en algunos casos (500mb a 5gb).

PyMapr
fuente
Hay otro ejemplo en el Blog de
GeoExamples
@bennos, probó el script en el blog pero devuelve un error de memoria al desempacar la matriz.
PyMapr
Le sugiero que discutir este problema con Roger Rovira i Veciana, el autor de la entrada, ya que él sabe que su código mejor que yo y tal vez sabe cómo resolver el problema
Bennos
Cambiar el ráster de entrada de 16 bits sin signo a 8 bits sin signo resolvió el problema de memoria. Sin embargo, lleva aproximadamente la misma cantidad de tiempo reclasificar que el script dmh126 a continuación.
PyMapr

Respuestas:

6

Aquí tienes un script de Python simple para la reclasificación, lo escribí y funciona para mí:

from osgeo import gdal

driver = gdal.GetDriverByName('GTiff')
file = gdal.Open('/home/user/workspace/raster.tif')
band = file.GetRasterBand(1)
lista = band.ReadAsArray()

# reclassification
for j in  range(file.RasterXSize):
    for i in  range(file.RasterYSize):
        if lista[i,j] < 200:
            lista[i,j] = 1
        elif 200 < lista[i,j] < 400:
            lista[i,j] = 2
        elif 400 < lista[i,j] < 600:
            lista[i,j] = 3
        elif 600 < lista[i,j] < 800:
            lista[i,j] = 4
        else:
            lista[i,j] = 5

# create new file
file2 = driver.Create( 'raster2.tif', file.RasterXSize , file.RasterYSize , 1)
file2.GetRasterBand(1).WriteArray(lista)

# spatial ref system
proj = file.GetProjection()
georef = file.GetGeoTransform()
file2.SetProjection(proj)
file2.SetGeoTransform(georef)
file2.FlushCache()

Solo cambia los rangos.

Espero que sea de ayuda.

dmh126
fuente
3
Debe cerrar file2con del file2o file2 = Nonepara asegurarse de que se escribe en el disco. .FlushCache()solo influye en el caché de teselas interno de GDAL.
Kersten
@ dmh126, modifiqué los rangos y el script funciona. Sin embargo, no es muy rápido (lo rápido es discutible). El ráster de entrada era de aproximadamente 120 mb y tardó aproximadamente 15 minutos en completarse. Con la ayuda de un paquete comercial de teledetección, lleva segundos. ¿Alguna recomendación sobre la disminución del tiempo de procesamiento?
PyMapr
Creo que el subprocesamiento múltiple puede ayudar. Puede intentar usar todos sus núcleos, hay una pregunta gis.stackexchange.com/questions/162978/…
dmh126
No tiene sentido usar un bucle doble para, vea la respuesta a continuación
Mattijn
Correcto, la reclasificación de doble bucle y por elemento es la más lenta de todas las formas posibles de hacer esto. Use las partes poderosas de numpy como ufuncs: docs.scipy.org/doc/numpy-1.10.1/reference/ufuncs.html .
sgillies
16

En lugar de hacer la reclasificación como un bucle doble para descrito por dmh126, hágalo usando np.where:

# reclassification    
lista[np.where( lista < 200 )] = 1
lista[np.where((200 < lista) & (lista < 400)) ] = 2
lista[np.where((400 < lista) & (lista < 600)) ] = 3
lista[np.where((600 < lista) & (lista < 800)) ] = 4
lista[np.where( lista > 800 )] = 5

En una matriz de 6163 por 3537 píxeles (41,6 mb), la clasificación se realiza en 1,59 segundos, donde se requieren 12 minutos y 41 s con el doble bucle for. En total, solo una aceleración de 478x.

En pocas palabras, nunca use un bucle doble para usar numpy

Mattijn
fuente
Gracias por la pista, pero creo que eso conducirá a un problema si las clases de entrada se superponen con las clases de salida. No quiero que la próxima regla cambie mi nuevo valor.
etrimaille
@ Industria - Acabo de encontrarme con ese problema aquí.
relima
Así que revise mi respuesta a continuación: gis.stackexchange.com/questions/163007/…
etrimaille
6

Aquí hay un ejemplo básico usando rasterio y numpy:

import rasterio as rio
import numpy as np


with rio.open('~/rasterio/tests/data/rgb1.tif') as src:
    # Read the raster into a (rows, cols, depth) array,
    # dstack this into a (depth, rows, cols) array,
    # the sum along the last axis (~= grayscale)
    grey = np.mean(np.dstack(src.read()), axis=2)

    # Read the file profile
    srcprof = src.profile.copy()

classes = 5
# Breaks is an array of the class breaks: [   0.   51.  102.  153.  204.]
breaks = (np.arange(classes) / float(classes)) * grey.max()

# classify the raster
classified = np.sum(np.dstack([(grey < b) for b in breaks]), axis=2).reshape(1, 400, 400).astype(np.int32)

# Update the file opts to one band
srcprof.update(count=1, nodata=None, dtype=classified.dtype)

with rio.open('/tmp/output.tif', 'w', **srcprof) as dst:
    # Write the output
    dst.write(classified)
Damon
fuente
2

Solo para completar la respuesta de @Mattijn, creo que eso conducirá a un problema si las clases de entrada se superponen con las clases de salida. No quiero que la próxima regla cambie mi nuevo valor.

No sé si pierdo velocidad, pero debería hacer una copia profunda:

list_dest = lista.copy()

list_dest[np.where( lista < 0 )] = 0
list_dest[np.where((0 <= lista) & (lista <= 1)) ] = 1
list_dest[np.where((1 < lista) & (lista <= 5)) ] = 2
list_dest[np.where( 5 < lista )] = 3
etrimaille
fuente
1

Aquí hay otro enfoque de Rasterio que pirateé juntos usando el Rasterio Cookbook y la respuesta de @ Mattijn.

import rasterio
import numpy as np

with rasterio.open('input_raster.tif') as src:    
    # Read as numpy array
    array = src.read()
    profile = src.profile

    # Reclassify
    array[np.where(array == 0)] = 4 
    array[np.where(array == 2)] = 1
    # and so on ...  

with rasterio.open('output_raster.tif', 'w', **profile) as dst:
    # Write to disk
    dst.write(array)
Aaron
fuente
0

En algunos casos, digitalizar numpy puede ser útil para pasar rápidamente de rangos a contenedores.

import rasterio
import numpy as np

with rasterio.open('my_raster.tif') as src:    
    array = src.read()
    profile = src.profile
    bins = np.array([-1.,-0.7,-0.4, 0.2, 1]) 
    inds = np.digitize(array, bins)

with rasterio.open('output_raster.tif', 'w', **profile) as dst:
    dst.write(inds)
Tactopoda
fuente
0

Con soporte de tabla de color ráster RGB:

import numpy as np
from osgeo import gdal

path_inDs = "/data/OCS_2016.extract.tif"
path_outDs = "/data/OCS_2016.postpython.tif"

driver = gdal.GetDriverByName('GTiff')
file = gdal.Open(path_inDs)

if file is None:
  print ('Could not open image file')
  sys.exit(1)

band = file.GetRasterBand(1)
lista = band.ReadAsArray()


# reclassification
lista[np.where(lista == 31)] = 16

# create new file
file2 = driver.Create(path_outDs, file.RasterXSize , file.RasterYSize , 1, gdal.GPI_RGB)
file2.GetRasterBand(1).WriteArray(lista)

# spatial ref system
proj = file.GetProjection()
georef = file.GetGeoTransform()
meta = file.GetMetadata()
colors = file.GetRasterBand(1).GetRasterColorTable()

file2.SetProjection(proj)
file2.SetGeoTransform(georef)
file2.SetMetadata(meta)
file2.GetRasterBand(1).SetRasterColorTable(colors)

file2.FlushCache()
del file2

Ser
fuente
0

Una alternativa ligeramente diferente podría ser la siguiente:

import numpy as np
from osgeo import gdal

original = gdal.Open('**PATH**\\origianl_raster.tif')



# read the original file

band = original.GetRasterBand(1) # assuming that the file has only one band
band_array = band.ReadAsArray()



#create a new array with reclassified values

new_array = np.where(band_array == np.nan, 4, 
                np.where(band_array == 0, 4,
                    np.where(band_array == 1, 1,
                        np.where(band_array == 2, 2,
                            np.where(band_array == 3, 3,
                                np.where(band_array == 4, 3, 
                                    np.where(band_array == 5, 4,
                                        np.where(band_array == 6, 5,
                                            np.where(band_array == 7, 5,
                                                np.where(band_array == 8, 6, 
                                                    np.where(band_array == 9, 7,
                                                       np.where(band_array == 10, 8,
                                                            np.where(band_array == 100, np.nan, np.nan))))))))))))) 
                                # the last line also includes values equal to 255, as they are the only ones left



# create and save reclassified raster as a new file

outDs = gdal.GetDriverByName('GTiff').Create("**PATH**\\reclassified_raster.tif", original.RasterXSize, original.RasterYSize, 1, gdal.GDT_Float32)

outBand = outDs.GetRasterBand(1)
outBand.WriteArray(new_array)

outDs.SetGeoTransform(original.GetGeoTransform())
outDs.SetProjection(original.GetProjection())


# flush cache

outDs.FlushCache()

Este script se reproduce con numpy.where ( https://docs.scipy.org/doc/numpy/reference/generated/numpy.where.html ): en todos los pasos, excepto el último, en lugar de devolver un valor cuando condición no se cumple, devuelve otro np.where. Y continúa hasta que se consideren todos los valores posibles del ráster.

Giallo
fuente