smoothSpline.R 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. #' Filter Time Series Imagery with a Cubic Spline
  2. #'
  3. #' @description
  4. #' This function uses the \code{\link{smooth.spline}} function to filter a
  5. #' vegetation index time serie of satellite data.
  6. #'
  7. #' @param x \code{RasterBrick} (or \code{RasterStack}) or \code{character} vector
  8. #' of filenames, sorted 'Vegetation index'.
  9. #' @param w \code{RasterBrick} (or \code{RasterStack}) with weighting
  10. #' information, e.g. derived from \code{\link{makeWeights}}.
  11. #' @param t In case of MODIS composite, the corresponding
  12. #' 'composite_day_of_the_year' \code{RasterBrick} (or \code{RasterStack}).
  13. #' @param groupYears \code{logical}. If \code{TRUE}, output files are grouped by
  14. #' years.
  15. #' @param timeInfo Result from \code{\link{orgTime}}.
  16. #' @param df \code{numeric}, yearly degree of freedom value passed to
  17. #' \code{\link{smooth.spline}}. If set as \code{character} (i.e., \code{df = "6"}),
  18. #' it is not adapted to the time serie length but used as a fixed value (see
  19. #' Details).
  20. #' @param outDirPath Output path, defaults to the current working directory.
  21. #' @param ... Arguments passed to \code{\link{writeRaster}}. Note that
  22. #' \code{filename} is created automatically.
  23. #'
  24. #' @return
  25. #' The filtered data and a text file with the dates of the output layers.
  26. #'
  27. #' @details
  28. #' \code{numeric} values of \code{df} (e.g., \code{df = 6)} are treated as
  29. #' yearly degrees of freedom. Here, the length of the input time series is not
  30. #' relevant since \code{df} is adapted to it with:
  31. #' \code{df*('length of _input_ timeserie in days'/365)}. The input length can
  32. #' differ from the output because of the \code{pillow} argument in
  33. #' \code{orgTime}.
  34. #'
  35. #' \code{character} values of \code{df} (e.g., \code{df = "6"}), on the other
  36. #' hand, are not adopted to the length of the input time series.
  37. #'
  38. #' @details
  39. #' Currently tested on MODIS and Landsat data. Using M*D13 data, it is also
  40. #' possible to use the 'composite_day_of_the_year' layer and the 'VI_Quality'
  41. #' layer. This function is currently under heavy development and a lot of
  42. #' changes are expected to come soon.
  43. #'
  44. #' @seealso
  45. #' \code{\link{whittaker.raster}}, \code{\link{raster}}.
  46. #'
  47. #' @author
  48. #' Matteo Mattiuzzi
  49. #'
  50. #' @examples
  51. #' \dontrun{
  52. #' # The full capacity of the following functions is currently avaliable only with M*D13 data.
  53. #' # !! The function is very new, double check the result!!
  54. #'
  55. #' # You need to extract the: 'vegetation index', 'VI_Quality layer',
  56. #' # and 'composite day of the year' layer.
  57. #' # runGdal(product="MOD13A2",begin="2004340",extent="sicily",end="2006070",
  58. #' # job="fullCapa",SDSstring="101000000010")
  59. #' # Afterward extract it to:
  60. #' options("MODIS_outDirPath")
  61. #'
  62. #' # the only obligatory dataset is "x" (vegetatino index), get the 'vi' data on the source directory:
  63. #' path <- paste0(options("MODIS_outDirPath"),"/fullCapa")
  64. #' vi <- preStack(path=path, pattern="*_NDVI.tif$")
  65. #'
  66. #' # "orgTime" detects timing information of the input data and generates based on the arguments
  67. #' # the output date information. For spline functions (in general) the beginning and
  68. #' # the end of the time series is always problematic.
  69. #' # So there is the argument "pillow" (default 75 days) that adds
  70. #' # (if available) some more layers on the two endings.
  71. #'
  72. #' timeInfo <- orgTime(vi,nDays=16,begin="2005001",end="2005365",pillow=40)
  73. #'
  74. #' # now re-run "preStack" with two diferences, 'files' (output of the first 'preStack' call)
  75. #' # and the 'timeInfo'.
  76. #' # Here only the data needed for the filtering is extractet:
  77. #' vi <- preStack(files=vi,timeInfo=timeInfo)
  78. #'
  79. #' smooth.spline.raster(x=vi,timeInfo=timeInfo)
  80. #'
  81. #' # Filter with weighting and time information:
  82. #' # if the files are M*D13 you can use also Quality layers and the composite day of the year:
  83. #' w <- stack(preStack(path=path, pattern="*_VI_Quality.tif$", timeInfo=timeInfo))
  84. #' w <- makeWeights(w,bitShift=2,bitMask=15,threshold=6)
  85. #' # you can also pass only the names
  86. #' t <- preStack(path=path, pattern="*_composite_day_of_the_year.tif$", timeInfo=timeInfo)
  87. #'
  88. #' smooth.spline.raster(x=vi,w=w,t=t,timeInfo=timeInfo)
  89. #' }
  90. #'
  91. #' @export smooth.spline.raster
  92. #' @name smooth.spline.raster
  93. smooth.spline.raster <- function(x, w=NULL, t=NULL, groupYears=TRUE, timeInfo = orgTime(x), df = 6,outDirPath = "./",...)
  94. {
  95. opt <- combineOptions(...)
  96. dir.create(opt$outDirPath,showWarnings=FALSE)
  97. outDirPath <- normalizePath(opt$outDirPath, winslash = "/", mustWork = TRUE)
  98. outDirPath <- setPath(outDirPath)
  99. # bitShift <- opts$bitShift
  100. # bitMask <- opts$bitMask
  101. # threshold <- opts$threshold
  102. dataFormat <- opt$dataFormat
  103. rasterOut <- toupper(writeFormats())
  104. if(dataFormat %in% rasterOut[,"name"])
  105. {
  106. dataFormat <- getExtension(dataFormat)
  107. } else
  108. {
  109. stop("Argument dataFormat='",dataFormat,"' in 'smooth.spline.raster()' is unknown/not supported. Please run 'writeFormats()' (column 'name') so list available dataFormat's")
  110. }
  111. if (!inherits(x, "Raster"))
  112. x <- raster::stack(x)
  113. if (!inherits(w, "Raster") & !is.null(w))
  114. w <- raster::stack(w)
  115. if (!inherits(t, "Raster") & !is.null(t))
  116. t <- raster::stack(t)
  117. tsLength <- as.numeric(max(timeInfo$inputLayerDates) - (min(timeInfo$inputLayerDates)-1))
  118. tsLayers <- length(unique(timeInfo$inputLayerDates))
  119. indf <- df
  120. if (is.character(df))
  121. {
  122. cat("Using fixed 'df':",df,"\n")
  123. nameDf <- "FixedDf"
  124. } else
  125. {
  126. df <- df*(tsLength/365)
  127. cat("Yearly 'df' is:",indf,"\nNow changed with df*('length of input data period in days'/365) to:",df,"\n")
  128. nameDf <- "YearlyDf"
  129. }
  130. df <- as.numeric(df)
  131. # TEMP
  132. b <- list()
  133. if (groupYears)
  134. {
  135. for (a in seq_along(unique(format(timeInfo$outputLayerDates,"%Y"))))
  136. {
  137. y <- unique(format(timeInfo$outputLayerDates,"%Y"))[a]
  138. b[[a]] <- brick(raster(x),nl=as.integer(sum(format(timeInfo$outputLayerDates,"%Y")==y)), values=FALSE)
  139. b[[a]] <- writeStart(b[[a]], filename=paste(opt$outDirPath,"/NDVI_",nameDf,indf,"_year",y,dataFormat,sep=""),...)
  140. }
  141. } else
  142. {
  143. b[[1]] <- brick(raster(x),nl=as.integer(length(timeInfo$outSeq)), values=FALSE)
  144. b[[1]] <- writeStart(b[[1]], filename=paste(opt$outDirPath,"/NDVI_",nameDf,indf,"_fullPeriod",dataFormat,sep=""),...)
  145. }
  146. tr <- blockSize(x)
  147. cat("Data is in, start processing!\n")
  148. ###############################
  149. # clusterFuns:
  150. clFun <- function(l)
  151. {
  152. minval <- -2000
  153. val <- getValues(x, row=tr$row[l], nrows=tr$nrows[l])
  154. mtrdim <- dim(val)
  155. set0 <- val <= minval # M.D13 specific!
  156. set0[is.na(val)] <- TRUE
  157. set0[rowSums(val,na.rm=TRUE)==0] <- TRUE
  158. if (!is.null(w))
  159. {
  160. wtu <- getValues(w, row=tr$row[l], nrows=tr$nrows[l])
  161. # is it a weight info?
  162. if(max(wtu) > 1)
  163. {
  164. bits <- detectBitInfo(vi,"VI usefulness",warn=FALSE)
  165. if(is.null(bits))
  166. {
  167. stop("Could not extract 'bits' for weighting from this product. Use 'makeWeights' function to generate weightings manualy!")
  168. }
  169. wtu <- makeWeights(wtu, bitShift = bits$bitShift, bitMask = bits$bitMask, decodeOnly = TRUE)
  170. }
  171. set0[wtu==0] <- TRUE
  172. } else
  173. {
  174. wtu <- matrix(1,nrow=mtrdim[1],ncol=mtrdim[2])
  175. }
  176. if (inherits(t,"Raster"))
  177. {
  178. inTu <- getValues(t, row=tr$row[l], nrows=tr$nrows[l])
  179. inTu <- repDoy(inTu,timeInfo,bias=timeInfo$inSeq[1]-1)
  180. set0[is.na(inTu)] <- TRUE
  181. inTu[set0] <- 0
  182. } else
  183. {
  184. inTu <- matrix(timeInfo$inSeq,nrow=mtrdim[1],ncol=mtrdim[2],byrow=TRUE)
  185. }
  186. wtu[set0] <- 0
  187. val[set0] <- 0
  188. r <- smooth.splineMtr(vali=val,wti=wtu,inTi=inTu,timeInfo=timeInfo,df=df)
  189. r[rowSums(abs(r))==0,] <- NA
  190. return(r)
  191. }
  192. for ( i in seq_along(tr$row) )
  193. {
  194. res <- clFun(i)
  195. res <- round(res)
  196. if (groupYears)
  197. {
  198. for (a in seq_along(unique(format(timeInfo$outputLayerDates,"%Y"))))
  199. {
  200. y <- unique(format(timeInfo$outputLayerDates,"%Y"))[a]
  201. b[[a]] <- writeValues(b[[a]], res[,format(timeInfo$outputLayerDates,"%Y")==y], tr$row[i])
  202. }
  203. } else
  204. {
  205. b[[1]] <- writeValues(b[[1]], res, tr$row[i])
  206. }
  207. }
  208. for (a in seq_along(b))
  209. {
  210. b[[a]] <- writeStop(b[[a]])
  211. if (groupYears)
  212. {
  213. y <- unique(format(timeInfo$outputLayerDates,"%Y"))[a]
  214. write.table(x=timeInfo$outputLayerDates[format(timeInfo$outputLayerDates,"%Y")==y], file=paste(opt$outDirPath,"/LayerDates_NDVI_",nameDf,indf,"_year",y,sep=""), row.names=FALSE, col.names=FALSE)
  215. } else
  216. {
  217. write.table(x=timeInfo$outputLayerDates, file=paste(opt$outDirPath,"/LayerDates_NDVI_",nameDf,indf,"fullPeriod",sep=""), col.names=FALSE, row.names=FALSE)
  218. }
  219. }
  220. return(NULL)
  221. }
  222. # vali=val;wti=wtu;inTi=inTu;timeInfo=timeInfo;df=df
  223. smooth.splineMtr <- function(vali,wti=NULL,inTi=NULL,timeInfo=NULL,df=NULL)
  224. {
  225. vali <- t(vali)
  226. yRow <- nrow(vali)
  227. yCol <- ncol(vali)
  228. if(is.null(wti))
  229. {
  230. wti <- matrix(1,nrow=yRow,ncol=yCol)
  231. } else {
  232. wti <- as.matrix(wti)
  233. wti <- t(wti)
  234. }
  235. if(is.null(inTi))
  236. {
  237. inTi <- matrix(1:yRow,ncol=yCol,nrow=yRow)
  238. } else
  239. {
  240. inTi <- as.matrix(inTi)
  241. # if inT is a fixed vector (i.e.: from filename of Landsat of length nrow(x) (==nlayer) create a matrix with 1:nlayer for each col.
  242. if(ncol(inTi)==1)
  243. {
  244. inTi <- matrix(inTi[,1],ncol=yCol,nrow=yRow)
  245. } else {
  246. inTi <- t(inTi)
  247. }
  248. }
  249. # generate output matrix
  250. if (is.null(timeInfo))
  251. {
  252. outTi <- inTi
  253. out <- matrix(NA, nrow=nrow(inTi), ncol=yCol)
  254. } else {
  255. outTi <- as.matrix(timeInfo$outSeq)
  256. if (ncol(outTi)==1)
  257. {
  258. outTi <- matrix(outTi, nrow=length(outTi), ncol=yCol)
  259. }
  260. out <- matrix(NA, nrow=nrow(outTi), ncol=yCol)
  261. }
  262. # minimum "minVal" input values for filtering
  263. Cvec <- (colSums(wti>0) > df)
  264. Cvec <- (1:yCol)[Cvec]
  265. for (u in Cvec)
  266. {
  267. s <- smooth.spline(y=vali[,u], x=inTi[,u], w=wti[,u], df=df, tol=1)
  268. out[,u] <- predict(s, outTi[,u])$y
  269. }
  270. return(t(out))
  271. }