Lire et reclasser efficacement de nombreux rasters dans R?

8

J'ai été chargé de créer une analyse d'aptitude des conditions de vagues dans le golfe du Mexique. J'ai environ 2 000 fichiers raster d'environ 8 Mo chacun (2438 colonnes, 1749 lignes, 1 km de cellule). Le paramètre dans les fichiers raster est la période d'onde et j'aimerais reclasser tous les rasters de telle sorte que si 4<= wave period <=9 then make cell = 1, sinon cellule = 0. Ensuite, résumez tous les rasters en un raster final et divisez par le nombre total de rasters pour donner un pourcentage total d'observations appropriées et résultat de l'exportation dans un format compatible ESRI ... peut-être quelque chose qui peut prendre en charge les flottants si besoin est. Je n'ai pas beaucoup travaillé avec Python ou R, mais après une recherche en ligne, il semble logique d'effectuer ce processus dans l'une de ces langues. J'ai trouvé du code jusqu'à présent dans R, mais je ne sais pas comment faire fonctionner cela.

library(rgdal)
raster_data <- list.files(path=getwd())    #promt user for dir containing raster files
num_files <- length(raster_data)
for (i in raster_data) {                   #read in rasters
   my_data <- readGDAL(raster_data[i])

À ce stade, je ne sais pas si je devrais également reclasser et commencer à additionner les données dans cette boucle ou non. Ma supposition serait oui car sinon je pense que je serais peut-être à court de mémoire sur mon ordinateur, mais tout simplement pas sûr. Je ne sais pas non plus comment reclasser les données.

Dans la recherche en ligne, je pense que j'utiliserais reclass(my_data, c(-Inf,3,0, 4,9,1, 10,Inf,0)), mais cela semble-t-il correct?

Et pour résumer, j'utiliserais sum(stack(my_data))et je ferais une sortie. Aussi ... si cela peut être effectué plus efficacement ou écrit en Python, je serais également ouvert à cela. Je suis vraiment un débutant en matière de programmation.

Nigel
la source
Utilisez simplement python-gdal. Ce sera beaucoup plus efficace dans votre cas.
SS_Rebelious
Merci, rebelle. Juste curieux de savoir pourquoi python-gdal est plus efficace dans cette situation? Serait-il également possible de voir certaines des étapes du code qui seraient nécessaires pour ce faire? Essayer de comprendre la meilleure façon de le faire tout en utilisant le moins de mémoire et de CPU possible, il est difficile de comprendre comment écrire le code de manière à ce qu'il lise les données, le traite, le supprime de la mémoire et puis passez au raster suivant.
Nigel
Je ne peux pas vous dire exactement pourquoi, mais la cause générale est que R a été conçu à d'autres fins et est connu pour fonctionner lentement avec des cycles. Quant à l'exemple de code, si personne ne le fournira, j'en partagerai un avec vous dans environ 10 heures lorsque j'aurai accès à la machine où le script correspondant est stocké.
SS_Rebelious

Réponses:

8

C'est une manière concise de le faire dans R --- ici sans fichiers intermédiaires:

library(raster)
raster_data <- list.files(path=getwd())    #promt user for dir containing raster files
s <- stack(raster_data)
f <- function(x) { rowSums(x >= 4 & x <= 9) }
x <- calc(s, f, progress='text', filename='output.tif')
Robert Hijmans
la source
1
+1 C'est bon pour les petits problèmes, mais faisons le calcul pour celui-ci: 2438 colonnes fois 1749 lignes fois 8 octets / valeur fois 2 mille grilles = 63,6 Go, qui Rdoivent tous rester en RAM pour créer s. (Probablement deux fois plus de RAM est nécessaire car sne remplacera probablement pas raster_data.) J'espère que vous avez beaucoup de RAM! Votre solution peut être rendue possible en divisant les 2000 grilles en petits groupes, en effectuant le calcul pour chaque groupe, puis en combinant ces calculs.
whuber
2
@whuber: 's' est un petit objet, juste un tas de pointeurs vers les fichiers. La fonction calc, comme les autres fonctions du package raster, ne chargera pas toutes les valeurs en mémoire; il les traitera en morceaux. Autrement dit, la répartition en groupes, comme vous le suggérez, se fait automatiquement, dans les coulisses. La taille du bloc peut être optimisée pour la quantité de RAM disponible via rasterOptions ().
Robert Hijmans
1
Merci d'avoir expliqué cela! J'avais supposé, sans vérifier, cela stacket calctravaillé comme la plupart des autres Rfonctions en chargeant d'abord toutes les données dans la RAM.
whuber
+1 Aimer la concision de R contre l'exemple Python fourni ...
SlowLearner
5

Comme je l'ai remarqué dans les commentaires, vous devez généralement éviter d'utiliser R à des fins non statistiques en raison de problèmes de performances dans certains aspects (l'utilisation des cycles est un exemple). Voici un exemple de code pour vous à Pyhton (grâce à cet article ) pour la reclassification d'un seul fichier avec une seule bande. Vous pourrez le modifier facilement pour le traitement par lots si vous savez comment obtenir tous les fichiers du répertoire . Notez que les rasters sont représentés comme des tableaux, vous pouvez donc utiliser des méthodes de tableau pour améliorer les performances, le cas échéant. Pour travailler avec des tableaux en Python, voir la documentation Numpy .

UPD: le code que j'ai publié initialement était une version tronquée d'un filtre personnalisé qui avait besoin d'un traitement par pixel. Mais pour cette question, l'utilisation de Numpy augmentera les performances (voir le code).

from osgeo import gdal
import sys
import numpy

gdalData = gdal.Open("path_to_file")
if gdalData is None:
  sys.exit("ERROR: can't open raster")

#print "Driver short name", gdalData.GetDriver().ShortName
#print "Driver long name", gdalData.GetDriver().LongName
#print "Raster size", gdalData.RasterXSize, "x", gdalData.RasterYSize
#print "Number of bands", gdalData.RasterCount
#print "Projection", gdalData.GetProjection()
#print "Geo transform", gdalData.GetGeoTransform()


raster = gdalData.ReadAsArray()
xsize = gdalData.RasterXSize
ysize = gdalData.RasterYSize

#print xsize, 'x', ysize

## per pixel processing
#for col in range(xsize):
#  for row in range(ysize):
#    # if pixel value is 16 - change it to 7
#    if raster[row, col] == 16:
#      raster[row, col] = 7

#reclassify raster values equal 16 to 7 using Numpy
temp = numpy.equal(raster, 16)
numpy.putmask(raster, temp, 7)

# write results to file (but at first check if we are able to write this format)
format = "GTiff"
driver = gdal.GetDriverByName(format)
metadata = driver.GetMetadata()
if metadata.has_key(gdal.DCAP_CREATE) and metadata[gdal.DCAP_CREATE] == "YES":
  pass
else:
  print "Driver %s does not support Create() method." % format
  sys.exit(1)
if metadata.has_key(gdal.DCAP_CREATECOPY) and metadata[gdal.DCAP_CREATECOPY] == "YES":
  pass
else:
  print "Driver %s does not support CreateCopy() method." % format
  sys.exit(1)

# we already have the raster with exact parameters that we need
# so we use CreateCopy() method instead of Create() to save our time
outData = driver.CreateCopy("path_to_new_file", gdalData, 0)
outData.GetRasterBand(1).WriteArray(raster)
SS_Rebelious
la source
4
Le "R est lent lors de l'exécution de cycles (boucles)" est souvent utilisé à mauvais escient comme raison d'éviter R. Oui, si vous boucliez sur les cellules d'un raster dans R, ce serait lent, mais le package raster fonctionne sur des rasters entiers à la fois , et a beaucoup de code C et fonctionne donc à la vitesse C. Pour un raster de cette taille, la plupart du travail se ferait à des vitesses C, la surcharge en boucle serait insignifiante.
Spacedman
@Spacedman, oui 'raster' est un paquet utile (et j'aime ça), mais je n'ai jamais été satisfait de ses performances même lorsque les boucles n'étaient pas impliquées.
SS_Rebelious
2
D'accord, comparez bien le temps qu'il faut en R avec le temps qu'il faut en Python. Ne pouvez-vous pas opérer sur un tableau numpy entier plutôt que de boucler?
Spacedman
@Spacedman, je viens de mettre à jour la réponse.
SS_Rebelious
Un grand merci à vous deux. Je vais essayer de bricoler à la fois le code Python que vous avez fourni et certains R et voir ce que je peux accomplir. Je mettrai à jour les résultats ou les problèmes.
Nigel
2
  1. N'utilisez pas readGDAL. Il lit dans un objet Spatial * qui pourrait ne pas être une bonne idée.

  2. Utilisez le rasterpackage. Il peut lire des choses GDAL dans des objets Raster. C’est une bonne chose. r = raster("/path/to/rasterfile.tif")le lira r.

  3. Votre classement est alors t = r > 4 & r <= 9

  4. La grande question est de savoir si vous devez les exporter vers de nouveaux fichiers raster, puis effectuer l'étape de résumé dans une autre boucle. Si vous avez le stockage, je les écrirais dans de nouveaux fichiers simplement parce que si votre boucle échoue (car l'un de ces 2000 fichiers est indésirable), vous devrez recommencer. Utilisez donc writeRasterpour créer des rasters seuillés si vous décidez de le faire.

  5. Votre boucle est alors juste quelque chose comme

cette.

count = raster(0,size of your raster)
for(i in 1:number of rasters){
  r = raster(path to binary raster file 'i')
  count = count + r
}
return(count)

La gestion de la mémoire de R pourrait vous frapper ici - quand vous le faites, count=count+rR pourrait bien faire une nouvelle copie de count. C'est donc potentiellement 2000 exemplaires - mais la collecte des ordures va démarrer et nettoyer, et c'est ici que le bouclage de R était très mauvais.

En termes de timing, sur mon PC 4 ans, le seuil op prend environ 1,5 s sur un raster de cette taille de nombres aléatoires compris entre zéro et vingt. Times 2000 = vous faites le calcul. Comme toujours, faites un petit ensemble de test pour développer le code, puis lancez-le sur vos données réelles et allez prendre un café.

Spacedman
la source
Je suis curieux de savoir ce que vous entendez par "le seuil op." Sur mon système (exécutant R2.15.0), la lecture d'une grille de 1,6 mégapixels (au format natif à virgule flottante ESRI) prend 0,19 seconde et l'exécution des cinq opérations de boucle - deux comparaisons, une conjonction, un ajout et un magasin - en prend une autre 0,09 seconde, pour 0,28 seconde par itération. Lorsque, au lieu de cela, la boucle est effectuée en mettant en cache 100 grilles à la fois dans un tableau et en utilisant rowSumspour faire les totaux, l'utilisation de la RAM augmente bien sûr: mais, tout aussi évidemment, il n'y a pas de récupération de place. Le chronométrage tombe seulement à 0,26 seconde par itération.
whuber