Reclassification raster à l'aide de python, gdal et numpy

9

Je voudrais reclasser un fichier raster d'un raster avec 10 classes en un raster avec 8 classes utilisant pyhton, gdal et / ou numpy. Les classes sont représentées sous forme d'entiers. J'ai essayé de suivre les étapes de ce post Reclassifier les rasters en utilisant GDAL et Python , le doc numpy.equal et également la doc gdal_calc. Cependant, en vain.

Le fichier raster à reclassifier a des valeurs entières allant de 0 à 11 et inclut également des valeurs 100 et 255. Les éléments suivants montrent la reclassification (de la valeur: à la valeur):

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,

Ce que j'ai pu faire, c'est sélectionner le fichier raster à reclasser à l'aide de tkinter.FileDialog et obtenir les informations raster telles que la géotransformation et la taille des pixels avec reclass = gdal.Open (raster, GA_ReadOnly).

Comment puis-je résoudre les problèmes ci-dessus.

Il convient de mentionner que les rasters à reclasser peuvent être assez importants dans certains cas (500 Mo à 5 Go).

PyMapr
la source
Il y a un autre exemple sur le blog
GeoExamples
@bennos, a essayé le script sur le blog mais il renvoie une erreur de mémoire lors du déballage du tableau.
PyMapr
Je vous suggère de discuter de ce problème avec Roger Veciana i Rovira, l'auteur de l'article, car il connaît son code mieux que moi et sait peut-être comment résoudre le problème
bennos
La modification du raster en entrée de 16 bits non signé à 8 bits non signé a résolu le problème de mémoire. Cependant, le reclassement prend environ le même temps que le script dmh126 ci-dessous.
PyMapr

Réponses:

6

Ici, vous avez un simple script python pour la reclassification, je l'ai écrit et cela fonctionne pour moi:

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

Modifiez simplement les plages.

J'espère que cela aidera.

dmh126
la source
3
Vous devez fermer file2avec del file2ou file2 = Nonepour vous assurer qu'il est écrit sur le disque. .FlushCache()n'influence que le cache de tuiles interne de GDAL.
Kersten du
@ dmh126, j'ai modifié les plages et le script fonctionne. Cependant, ce n'est pas très rapide (rapide étant discutable). Le raster en entrée mesurait environ 120 Mo et prenait environ 15 minutes. À l'aide d'un logiciel de télédétection commercial, cela prend quelques secondes. Des recommandations sur la réduction du temps de traitement?
PyMapr
Je pense que le multithreading peut aider. Vous pouvez essayer d'utiliser tous vos cœurs, il y a une question gis.stackexchange.com/questions/162978/…
dmh126
Cela n'a pas de sens d'utiliser un double pour la boucle, voir la réponse ci
Mattijn
À droite, la reclassification par boucle double et par élément est la manière la plus lente de procéder. Utilisez les parties puissantes de numpy comme ufuncs: docs.scipy.org/doc/numpy-1.10.1/reference/ufuncs.html .
sgillies
16

Au lieu de faire la reclassification en double boucle for décrite par dmh126, faites-le en utilisant 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

Sur un tableau de 6163 par 3537 pixels (41,6 Mo), la classification se fait en 1,59 secondes, où il faut 12min 41s en utilisant la boucle double for. Au total, juste une accélération de 478x.

En résumé, n'utilisez jamais de boucle double pour numpy

Mattijn
la source
1
Merci pour cet indice, mais je pense que cela entraînera un problème si les classes d'entrée se chevauchent avec les classes de sortie. Je ne veux pas que ma nouvelle valeur soit modifiée par la règle suivante.
etrimaille
@Gustry - Je viens de rencontrer ce problème ici.
relima
1
Alors vérifiez ma réponse ci-dessous: gis.stackexchange.com/questions/163007/…
etrimaille
6

Voici un exemple de base utilisant rasterio et 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
la source
3

Juste pour compléter la réponse de @Mattijn, je pense que cela entraînera un problème si les classes d'entrée se chevauchent avec les classes de sortie. Je ne veux pas que ma nouvelle valeur soit modifiée par la règle suivante.

Je ne sais pas si je perds de la vitesse mais je devrais faire une copie complète:

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
la source
1

Voici une autre approche Rasterio que j'ai piratée ensemble en utilisant le livre de recettes Rasterio et la réponse 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
la source
0

Dans certains cas, numériser numpy peut être utile pour passer rapidement des plages aux bacs.

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
la source
0

Avec la prise en charge de la table des couleurs RVB 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
la source
0

Une alternative légèrement différente pourrait être la suivante:

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

Ce script joue avec numpy.where ( https://docs.scipy.org/doc/numpy/reference/generated/numpy.where.html ): dans toutes les étapes en dehors de la dernière, au lieu de renvoyer une valeur lorsque le n'est pas satisfaite, elle renvoie un autre np.where. Et cela continue jusqu'à ce que toutes les valeurs possibles du raster soient prises en compte.

Giallo
la source