Raster reclassificar usando python, gdal e numpy

9

Gostaria de reclassificar um arquivo rasterizado de um raster com 10 classes para um raster com 8 classes usando pyhton, gdal e / ou numpy. As classes são representadas como números inteiros. Eu tentei seguir as etapas deste post Reclassificar rasters usando GDAL e Python , o numpy.equal doc e também gdal_calc doc. No entanto, sem sucesso.

O arquivo rasterizado a ser reclassificado possui valores inteiros que variam de 0 a 11 e também incluem os valores 100 e 255. A seguir, é apresentada a reclasse (de valor: para 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:

O que eu pude fazer é selecionar o arquivo rasterizado a ser reclassificado usando tkinter.FileDialog e obter informações rasterizadas, como geotransformações e tamanho de pixel com reclass = gdal.Open (raster, GA_ReadOnly).

Como faço para resolver o exposto acima.

Vale a pena mencionar que os rasters a serem reclassificados podem ser razoavelmente grandes em alguns casos (500mb a 5gb).

PyMapr
fonte
Há outro exemplo no Blog
bennos
@bennos, tentei o script no blog, mas ele retorna um erro de memória ao descompactar a matriz.
PyMapr 17/09/2015
Eu sugiro que você discutir este problema com Roger Veciana i Rovira, o autor do post, como ele sabe o seu código melhor do que eu e talvez sabe como resolver o problema
Bennos
Alterar a varredura de entrada de 16 bits não assinado para 8 bits não assinado resolveu o problema de memória. No entanto, leva aproximadamente a mesma quantidade de tempo para reclassificar o script dmh126 abaixo.
PyMapr 17/09/2015

Respostas:

6

Aqui você tem um script python simples para reclassificação, eu escrevi e funciona para mim:

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()

Apenas mude os intervalos.

Eu espero que isso ajude.

dmh126
fonte
3
Você deve fechar file2com del file2ou file2 = Nonepara garantir que ele seja gravado no disco. .FlushCache()influencia apenas o cache interno do bloco GDALs.
Kersten
@ dmh126, modifiquei os intervalos e o script funciona. No entanto, não é muito rápido (sendo rapidamente discutível). A varredura de entrada tinha cerca de 120mb e levou cerca de 15 minutos para ser concluída. Com o auxílio de um pacote de sensoriamento remoto comercial, leva segundos. Alguma recomendação para diminuir o tempo de processamento?
PyMapr 17/09/2015
Eu acho que o multithreading pode ajudar. Você pode tentar usar todos os seus núcleos, há uma questão gis.stackexchange.com/questions/162978/...
dmh126
Não faz sentido usar um double loop for, veja resposta abaixo
Mattijn
Certo, a reclassificação de loop duplo e por elemento é a mais lenta de todas as maneiras possíveis de fazer isso. Use as partes poderosas do numpy como ufuncs: docs.scipy.org/doc/numpy-1.10.1/reference/ufuncs.html .
Sgillies
16

Em vez de fazer a reclassificação como um loop for duplo descrito por dmh126, faça-o 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

Em uma matriz de 6163 por 3537 pixels (41,6mb), a classificação é feita em 1,59 segundos, onde são necessários 12min 41s usando o loop for duplo. No total, apenas uma aceleração de 478x.

Bottomline, nunca use um loop for duplo usando numpy

Mattijn
fonte
Obrigado pela dica, mas acho que isso causará um problema se as classes de entrada se sobreporem às classes de saída. Não quero que meu novo valor seja alterado pela próxima regra.
Etrimaille
@ Indústria - Apenas encontrei esse problema aqui.
Relima
Portanto, verifique minha resposta abaixo: gis.stackexchange.com/questions/163007/…
etrimaille
6

Aqui está um exemplo básico usando rasterio e 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
fonte
2

Apenas para completar a resposta de @Mattijn, acho que isso causará um problema se as classes de entrada se sobreporem às classes de saída. Não quero que meu novo valor seja alterado pela próxima regra.

Não sei se perco a velocidade, mas devo fazer uma cópia 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
fonte
1

Aqui está outra abordagem do Rasterio que eu hackeei usando o Rasterio Cookbook e a resposta 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
fonte
0

Em alguns casos, a digitalização numpy pode ser útil para ir rapidamente dos intervalos aos compartimentos.

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
fonte
0

Com suporte para tabela de cores RGB raster:

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
fonte
0

Uma alternativa ligeiramente diferente pode ser a seguinte:

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()

Esse script é reproduzido com numpy.where ( https://docs.scipy.org/doc/numpy/reference/generated/numpy.where.html ): em todas as etapas além da última, em vez de retornar um valor quando o condição não for atendida, ele retorna outro np.where. E continua até que todos os valores possíveis da varredura sejam considerados.

Giallo
fonte