QualityCheck.R 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. QualityCheck <-
  2. function(Data, Product, Band, NoDataFill, QualityBand, QualityScores, QualityThreshold)
  3. {
  4. ##### Define what valid ranges for quality bands should be for different products:
  5. # 1=lower range 2=upper range 3=no fill value
  6. QA_RANGE <- data.frame(
  7. MOD09A1 = c(0,4294966531,NA), # Surface reflectance bands 0-1 bits
  8. MYD09A1 = c(0,4294966531,NA), # Surface reflectance bands 0-1 bits
  9. MOD11A2 = c(0,255,NA), # Land surface temperature and emissivity 0-1 bits
  10. MYD11A2 = c(0,255,NA), # Land surface temperature and emissivity 0-1 bits
  11. MCD12Q1 = c(0,254,255), # Land cover types 0-1 bits
  12. MOD13Q1 = c(0,3,-1), # Vegetation indices 0-1 bits
  13. MYD13Q1 = c(0,3,-1), # Vegetation indices 0-1 bits
  14. MOD15A2 = c(0,254,255), # LAI - FPAR 0 bit
  15. MYD15A2 = c(0,254,255), # LAI - FPAR 0 bit
  16. MOD17A2 = c(0,254,255), # GPP 0 bit
  17. MOD17A3 = c(0,100,NA), # GPP funny one - evaluate separately
  18. MCD43A4 = c(0,4294967294,4294967295), # BRDF albedo band quality, taken from MCD43A2, for reflectance data
  19. MOD16A2 = c(0,254,255) # 8-day, monthly ET/LE/PET/PLE, annual LE/PLE. Same data as MOD15A2 QC
  20. )
  21. row.names(QA_RANGE) <- c("min","max","noData")
  22. # Land cover dynamics products are available for download but not for quality checking with this function.
  23. # Check the product input corresponds to one with useable quality information
  24. if(!any(names(QA_RANGE) == Product)) stop("QualityCheck cannot be used for ",Product," product.")
  25. # If dataframes, coerce to matrices.
  26. if(is.data.frame(Data)) Data <- as.matrix(Data)
  27. if(is.data.frame(QualityScores)) QualityScores <- as.matrix(QualityScores)
  28. # Check that Data and QualityScores have matching length and, if a matrix, dimensions .
  29. if(is.matrix(Data) | is.matrix(QualityScores)){
  30. if(!(is.matrix(Data) & is.matrix(QualityScores))) stop("Data and QualityScores do not have matching dimensions.")
  31. if(!all(dim(Data) == dim(QualityScores))) stop("Data and QualityScores do not have matching dimensions.")
  32. } else {
  33. if(length(Data) != length(QualityScores)) stop("Data and QualityScores must have matching lengths.")
  34. }
  35. # Check the QualityScores input are within the correct range for the product requested.
  36. if(is.na(QA_RANGE["noData",Product])){
  37. if(any(QualityScores < QA_RANGE["min",Product] | QualityScores > QA_RANGE["max",Product]))
  38. stop("QualityScores not all in range of ",Product,"'s QC: ",QA_RANGE["min",Product],"-",QA_RANGE["max",Product])
  39. } else {
  40. qualityScoresInvalid <- ifelse(QualityScores != QA_RANGE["noData",Product],
  41. QualityScores < QA_RANGE["min",Product] | QualityScores > QA_RANGE["max",Product],
  42. FALSE)
  43. if(any(qualityScoresInvalid))
  44. stop("QualityScores not all in range of ",Product,"'s QC: ",QA_RANGE["min",Product],"-",QA_RANGE["max",Product])
  45. }
  46. # Quality Threshold should be one integer.
  47. if(length(QualityThreshold) != 1) stop("QualityThreshold input must be one integer.")
  48. if(!is.numeric(QualityThreshold)) stop("QualityThreshold should be numeric class. One integer.")
  49. if(abs(QualityThreshold - round(QualityThreshold)) > .Machine$double.eps^0.5){
  50. stop("QualityThreshold input must be one integer.")
  51. }
  52. # NoDataFill should be one integer.
  53. if(length(NoDataFill) != 1) stop("NoDataFill input must be one integer.")
  54. if(!is.numeric(NoDataFill)) stop("NoDataFill should be numeric class. One integer.")
  55. if(abs(NoDataFill - round(NoDataFill)) > .Machine$double.eps^0.5) stop("NoDataFill input must be one integer.")
  56. # Check QualityThreshold is within 0-3 for all except LAI, ET and GPP bands, which must be within 0-1, and MOD17A3 (0-100).
  57. lai.gpp.prods <- c("MOD15A2", "MYD15A2", "MOD16A2", "MOD17A2")
  58. if(any(lai.gpp.prods == Product)){
  59. if(QualityThreshold != 0 & QualityThreshold != 1){
  60. stop("QualityThreshold should be either 0 or 1 for this product; 0 is good quality, 1 is other.")
  61. }
  62. } else if(Product != "MOD17A3"){
  63. if(QualityThreshold < 0 | QualityThreshold > 3){
  64. stop("QualityThreshold should be between 0-3 for this product;
  65. 0 = high quality, 1 = good but marginal quality, 2 = cloudy/poor quality, 3 = poor quality for other reasons.")
  66. }
  67. }
  68. # Check whether all QualityScores are no data fills scores.
  69. # If so, then return Data with NoDataFill removed, without quality screening.
  70. if(all(QualityScores == QA_RANGE["noData",Product])){
  71. warning("Quality checking aborted for this subset because all QualityScores were missing data.",
  72. call.=FALSE, immediate.=TRUE)
  73. return(matrix(ifelse(Data != NoDataFill, Data, NA), nrow = nrow(Data)))
  74. }
  75. #####
  76. # MOD17A3 is an exception, so deal with this first, and then the rest,
  77. if(Product == "MOD17A3"){
  78. if(QualityThreshold < 0 | QualityThreshold > 100) stop("QualityThreshold should be between 0-100 for this product.")
  79. NOFILLRANGE <- c(250, 255)
  80. Data <- ifelse(Data > NOFILLRANGE[1] & Data < NOFILLRANGE[2], Data, NA)
  81. Data <- ifelse(QualityScores <= QualityThreshold, Data, NA)
  82. } else {
  83. # Convert decimal QualityScores values into binary.
  84. decimal.set <- QualityScores
  85. if(max(QualityScores) == 0) num.binary.digits <- 1
  86. if(max(QualityScores) != 0) num.binary.digits <- floor(log(max(QualityScores), base = 2)) + 1
  87. binary.set<- matrix(nrow = length(QualityScores), ncol = num.binary.digits)
  88. for(n in 1:num.binary.digits){
  89. binary.set[ ,(num.binary.digits - n) + 1] <- decimal.set %% 2
  90. decimal.set <- decimal.set %/% 2
  91. }
  92. quality.binary <- apply(binary.set, 1, function(x) paste(x, collapse = ""))
  93. # Deal with the quality data in binary, according to the product input.
  94. if(any(lai.gpp.prods == Product)){
  95. qa.binary <- as.numeric(substr(quality.binary, nchar(quality.binary), nchar(quality.binary)))
  96. Data <- ifelse(Data != NoDataFill & qa.binary <= QualityThreshold, Data, NA)
  97. } else {
  98. # Create an ifelse here so if MCD43A4, snip the relevant binary string for Band. Otherwise, carry on.
  99. if(Product == "MCD43A4"){
  100. band.num <- as.numeric(substr(Band, nchar(Band), nchar(Band)))
  101. if(any(is.na(band.num))) stop("Band input is not one of the reflectance bands (1-7) from MCD43A4.")
  102. if(any(1 < band.num & band.num > 7)) stop("Band input is not one of the reflectance bands (1-7) from MCD43A4.")
  103. # Select the section of binary code relevant to Band.
  104. qa.binary <- substr(quality.binary, (nchar(quality.binary) - (((band.num - 1) * 2) + 2)),
  105. (nchar(quality.binary) - ((band.num - 1) * 2)))
  106. qa.int <- numeric(length(qa.binary))
  107. qa.int[qa.binary == "000"] <- 0
  108. qa.int[qa.binary == "001"] <- 1
  109. qa.int[qa.binary == "010"] <- 2
  110. qa.int[qa.binary == "011"] <- 3
  111. qa.int[qa.binary == "100"] <- 4
  112. Data <- ifelse(Data != NoDataFill & qa.int <= QualityThreshold, Data, NA)
  113. } else {
  114. qa.binary <- substr(quality.binary, nchar(quality.binary) - 1, nchar(quality.binary))
  115. qa.int <- numeric(length(qa.binary))
  116. qa.int[qa.binary == "00"] <- 0
  117. qa.int[qa.binary == "01"] <- 1
  118. qa.int[qa.binary == "10"] <- 2
  119. qa.int[qa.binary == "11"] <- 3
  120. Data <- ifelse(Data != NoDataFill & qa.int <= QualityThreshold, Data, NA)
  121. }
  122. }
  123. }
  124. return(Data)
  125. }