¿Leyendo y reclasificando eficientemente muchos rásteres en R?

8

Me encargaron crear un análisis de idoneidad de las condiciones de las olas en el Golfo de México. Tengo aproximadamente 2 mil archivos ráster que son de aproximadamente 8 MB cada uno (2438 columnas, 1749 filas, 1 km de tamaño de celda). El parámetro en los archivos ráster es el período de onda y me gustaría reclasificar todos los rásteres de modo que si 4<= wave period <=9 then make cell = 1, de lo contrario, la celda = 0. Luego, sume todos los rásteres en un ráster final y divida por el número total de rásteres para obtener un porcentaje total de observaciones adecuadas y resultados de exportación en algún formato compatible con ESRI ... tal vez algo que pueda admitir flotantes si es necesario. No he trabajado mucho con Python o R, pero después de buscar en línea parece tener sentido realizar este proceso en uno de esos idiomas. Se me ocurrió un código hasta ahora en R, pero estoy confundido sobre cómo hacer que esto funcione.

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

En este punto, estoy confundido sobre si también debería reclasificar y comenzar a sumar los datos dentro de este ciclo o no. Supongo que sí, ya que de lo contrario creo que posiblemente me quedaría sin memoria en mi computadora, pero no estoy seguro. Tampoco estoy seguro de cómo reclasificar los datos.

Al investigar en línea, creo que usaría reclass(my_data, c(-Inf,3,0, 4,9,1, 10,Inf,0)), pero ¿se ve bien?

Y para resumir, usaría sum(stack(my_data))y de alguna manera generaría eso. Además ... si esto pudiera realizarse o escribirse de manera más eficiente en Python, también estaría abierto a eso. Realmente soy un principiante cuando se trata de programación.

Nigel
fuente
Solo usa python-gdal. Será mucho más eficiente en su caso.
SS_Rebelious
Gracias rebelde. ¿Tienes curiosidad por saber por qué python-gdal es más eficiente en esta situación? ¿También sería posible ver algunos de los pasos en el código que serían necesarios para hacer esto? Tratar de descubrir la mejor manera de hacer esto mientras se utiliza la menor cantidad de memoria y CPU posible, es confuso descubrir cómo escribir el código de manera que se lea en los datos, el proceso, lo saque de la memoria y luego pasar al siguiente ráster.
Nigel
No puedo decir exactamente por qué, pero la causa general es que R fue diseñado para otros fines y se sabe que funciona lentamente con los ciclos. En cuanto al código de ejemplo, si nadie lo proporciona, compartiré uno con usted en aproximadamente 10 horas cuando tenga acceso a la máquina donde se almacena el script correspondiente.
SS_Rebelious

Respuestas:

8

Esta es una manera concisa de hacer eso en R --- aquí sin archivos intermedios:

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
fuente
1
+1 Esto es bueno para pequeños problemas, pero hagamos los cálculos para este: 2438 columnas por 1749 filas por 8 bytes / valor por 2 mil cuadrículas = 63,6 GB, todo lo cual Rdebe mantenerse en la RAM para crear s. (Probablemente se necesita el doble de RAM porque sprobablemente no se reemplazará raster_data). ¡Espero que tenga mucha RAM! Su solución se puede hacer realidad dividiendo las cuadrículas de 2000 en grupos más pequeños, realizando el cálculo para cada grupo y luego combinando esos cálculos.
whuber
2
@whuber: 's' es un objeto pequeño, solo un montón de punteros a los archivos. La función calc, como otras funciones en el paquete ráster, no cargará todos los valores en la memoria; los procesará en trozos. Es decir, la división en grupos, como sugiere, se realiza automáticamente, detrás de escena. El tamaño del fragmento se puede optimizar para la cantidad de RAM disponible a través de rasterOptions ().
Robert Hijmans
1
¡Gracias por explicar eso! Asumí, sin verificar, eso stacky calctrabajé como la mayoría de las otras Rfunciones al cargar primero todos los datos en la RAM.
Whuber
+1
Me gusta
5

Como noté en los comentarios, generalmente debe evitar el uso de R para fines no estadísticos debido a problemas de rendimiento en ciertos aspectos (trabajar con ciclos es un ejemplo). Aquí hay un ejemplo de código para usted en Pyhton (gracias a este artículo ) para la reclasificación de un solo archivo con una sola banda. Podrá modificarlo fácilmente para el procesamiento por lotes si sabe cómo obtener todos los archivos del directorio . Tenga en cuenta que los rásteres se representan como matrices, por lo que puede usar métodos de matriz para mejorar el rendimiento cuando corresponda. Para trabajar con matrices en Python, consulte la documentación de Numpy .

UPD: el código que publiqué inicialmente era una versión truncada de un filtro personalizado que necesitaba procesamiento por píxel. Pero para esta pregunta, el uso de Numpy aumentará el rendimiento (ver código).

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
fuente
44
La "R es lenta cuando se hacen ciclos (bucles)" a menudo se usa incorrectamente como una razón para evitar R. Sí, si se pasa sobre las celdas de un ráster en R sería lento, pero el paquete de ráster funciona en rásteres completos a la vez , y tiene mucho código C, por lo que se ejecuta a velocidad C. Para un ráster de ese tamaño, la mayor parte del trabajo sería a velocidades C, la sobrecarga de bucle sería insignificante.
Spacedman
@Spacedman, sí 'raster' es un paquete útil (y me gusta), pero nunca estuve satisfecho con su rendimiento incluso cuando los bucles no estaban involucrados.
SS_Rebelious
2
Bien, compara el tiempo que lleva en R con el tiempo que lleva en Python. ¿No puede operar en una matriz completa en lugar de bucle?
Spacedman
@Spacedman, acabo de actualizar la respuesta.
SS_Rebelious
Muchas gracias a los dos. Voy a intentar jugar tanto con el código de Python que proporcionaste como con algunos R y veré qué puedo lograr. Actualizaré con resultados o problemas.
Nigel
2
  1. No uses readGDAL. Se lee en un objeto espacial * que podría no ser una buena idea.

  2. Usa el rasterpaquete. Puede leer cosas GDAL en objetos Raster. Estas son una buena cosa. r = raster("/path/to/rasterfile.tif")lo leerá en r.

  3. Tu clasificación es entonces t = r > 4 & r <= 9

  4. La gran pregunta es si enviarlos a nuevos archivos ráster y luego hacer el paso de resumen en otro bucle. Si tiene el almacenamiento, los escribiría en archivos nuevos solo porque si su bucle falla (porque uno de esos 2000 archivos es basura) tendrá que comenzar de nuevo. Por lo tanto, use writeRasterpara crear rásteres trillados si decide hacerlo.

  5. Tu ciclo es entonces algo así como

esta.

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 administración de la memoria de R podría afectarlo aquí; cuando lo haga, count=count+rR bien podría hacer una nueva copia de count. Por lo tanto, eso es potencialmente 2000 copias, pero la recolección de basura se activará y limpiará, y es aquí donde el bucle de R solía ser muy malo.

En términos de tiempo, en mi PC de 4 años, el umbral de operación toma alrededor de 1.5 segundos en una trama de ese tamaño de números aleatorios entre cero y veinte. Veces 2000 = haces los cálculos. Como siempre, haga un pequeño conjunto de prueba para desarrollar el código, luego ejecútelo en sus datos reales y tome un café.

Hombre espacial
fuente
Tengo curiosidad sobre lo que quieres decir con "el umbral op". En mi sistema (ejecutando R2.15.0), leer una cuadrícula de 1.6 megapíxeles (en formato de punto flotante ESRI nativo) toma 0.19 segundos y realizar las operaciones de cinco bucles (dos comparaciones, una conjunción, una adición y una tienda) toma otro 0.09 segundos, durante 0.28 segundos por iteración. Cuando, en cambio, el bucle se realiza almacenando en caché 100 cuadrículas a la vez en una matriz y utilizando rowSumspara hacer los totales, el uso de RAM, por supuesto, aumenta: pero, igualmente obvio, no hay recolección de basura. El tiempo cae solo a 0.26 segundos por iteración.
Whuber