|
| 1 | +--- |
| 2 | +title: "Third Walkthrough Notes" |
| 3 | +author: "Kristina Riemer" |
| 4 | +output: github_document |
| 5 | +urlcolor: blue |
| 6 | +--- |
| 7 | + |
| 8 | +summarize what happened last time |
| 9 | + |
| 10 | +## Video 1: Plot Single RGB Image |
| 11 | + |
| 12 | +Go to globus.org and login. In Terraref Collection, go to ua-mac for the Arizona site data, Level_1_Plots for data organized by date, and rgb_geotiff for RGB images. We want to look at single image for May 1, 2018 in a particular plot. Select 2018-05-01 and then MAC Field Scanner Season 6 Range 19 Column 1. Select a random file to download. Use Transfer or Sync to button and select own endpoint and Downloads folder to transfer to computer. |
| 13 | + |
| 14 | +While that's downloading, which will take a minute, open up Vice app in CyVerse Discovery Environment. Use instance of TERRA REF Rstudio 3.6.0 that's already running. Select Import button in RStudio IDE and browse to location of newly downloaded file and hit OK. This will take a moment to load. (Might need to plot locally because it may take too long) |
| 15 | + |
| 16 | +Geotiff files are gridded spatial data, which are of raster format. So use raster R package to read into R using `raster`. And then can look at how it looks with `plot`. |
| 17 | + |
| 18 | +```{r, eval=FALSE} |
| 19 | +library(raster) |
| 20 | +single_RGB <- raster("rgb_geotiff_L1_ua-mac_2018-05-01__11-24-40-779_left.tif") |
| 21 | +plot(single_RGB) |
| 22 | +``` |
| 23 | + |
| 24 | +## Video 2: Access Full Field Images |
| 25 | + |
| 26 | +That's just a single image taken in that particular plot on the first of May. If we want all the images from a day combined, there's processed data files that hold that in Clowder. |
| 27 | + |
| 28 | +Go to [https://terraref.ncsa.illinois.edu/clowder/](https://terraref.ncsa.illinois.edu/clowder/) to access Clowder. We're using all public images so do not need to log in. Navigate to a file by going Spaces -> Sample Data 2019 -> (may have to "View All Datasets") Season 6 May 2018 full field RGB Geotiffs -> click on first one. |
| 29 | + |
| 30 | +This is called a mosiac. Combines all the images from that day, even if they aren't continous. This is a lower resolution version of all images, it would be a much larger file if not. The file we just plotted is within this. |
| 31 | + |
| 32 | +We could download this file manually using Download button. Instead, going to do programmatically using Python like we did last time (to set us up later to download a set of these). |
| 33 | + |
| 34 | +Go to Vice app. In command line (Terminal tab), start up Python. First set up connection to URL with requests library. Type in the first part of the URL, and then copy and paste the unique part. |
| 35 | + |
| 36 | +```{python, eval=FALSE} |
| 37 | +python3 |
| 38 | +import requests |
| 39 | +
|
| 40 | +file_url = 'https://terraref.ncsa.illinois.edu/clowder/api/files/5c81a03e4f0c0ca8052b2635?dataset=5c81709a4f0c78f6486d686c&space=5c50512a4f0c436195b9ad67' |
| 41 | +api_key = {'key': ''} |
| 42 | +file_request = requests.get(file_url, api_key) |
| 43 | +file_request |
| 44 | +``` |
| 45 | + |
| 46 | +Then read in `open` function. Set up file name by copy and pasting from file page. Read in in chunks, which will take a minute. |
| 47 | + |
| 48 | +```{python, eval=FALSE} |
| 49 | +from io import open |
| 50 | +
|
| 51 | +file_name = 'fullfield_L1_ua-mac_2018-05-01_rgb_stereovis_ir_sensors_fullfield_sorghum6_shade_may2018_thumb.tif' |
| 52 | +with open(file_name, 'wb') as object: |
| 53 | + for chunk in file_request.iter_content(chunk_size=1024): |
| 54 | + if chunk: |
| 55 | + object.write(chunk) |
| 56 | +``` |
| 57 | + |
| 58 | +We can then plot this like we did for the other RGB image. Start new R script to hold all code. Because this is a larger image, it will take a minute to plot. Can see that long and skinny shape of the entire field. |
| 59 | + |
| 60 | +```{r, eval=FALSE} |
| 61 | +library(raster) |
| 62 | +full_field <- raster("fullfield_L1_ua-mac_2018-05-01_rgb_stereovis_ir_sensors_fullfield_sorghum6_shade_may2018_thumb.tif") |
| 63 | +plot(full_field) |
| 64 | +``` |
| 65 | + |
| 66 | +## Video 3: Clip Full Field Image |
| 67 | + |
| 68 | +We're only interested in that one plot that we looked at an image from before. So we need to clip this raster down to only the extent of that plot. |
| 69 | + |
| 70 | +We need to get a vector for that plot. We will pull the site information using the `traits` package like we did before. Set up options and use `betydb_query`. |
| 71 | + |
| 72 | +First get plot vector. Now pulling site info from Bety, like we did for trait data. |
| 73 | + |
| 74 | +```{r, eval=FALSE} |
| 75 | +library(traits) |
| 76 | +options(betydb_url = "https://terraref.ncsa.illinois.edu/bety/", |
| 77 | + betydb_api_version = 'beta', |
| 78 | + betydb_key = '9999999999999999999999999999999999999999') |
| 79 | +
|
| 80 | +plot_data <- betydb_query(table = "sites", |
| 81 | + sitename = "MAC Field Scanner Season 6 Range 19 Column 10") |
| 82 | +``` |
| 83 | + |
| 84 | +We can look at that dataframe, contains all the information for that location, including site and plot info. |
| 85 | + |
| 86 | +These data are pulled from our database, which is called BETYdb. If Google "terra ref betydb", it will be the first hit. This is a pretty nice interface to data, and another way to explore what is available. There is a schema for this database, which contains all the tables and info about their columns, data types, and relationships. Click Docs and Schema to look at this. If we click on Sites table, then can see overlap with column names. This can be a useful tool. |
| 87 | + |
| 88 | +Need to make a spatial object out of the plot info. Use sf package to do this, which is the vector analogue of raster package. First function include argument to set coordinate reference system as WGS84. Multipolygon object to points to spatial object. |
| 89 | +```{r, eval=FALSE} |
| 90 | +library(sf) |
| 91 | +site_shape <- st_as_sfc(plot_data$geometry, crs = 4326) |
| 92 | +site_poly <- st_cast(site_shape, "POINT") |
| 93 | +site_clip <- as(site_poly, "Spatial") |
| 94 | +``` |
| 95 | + |
| 96 | +Replot fullfield with plot vertices. Can do sanity check for this location by comparing to traitvis app map. |
| 97 | + |
| 98 | +```{r, eval=FALSE} |
| 99 | +plot(full_field) |
| 100 | +points(site.clip) |
| 101 | +``` |
| 102 | + |
| 103 | +Can use `crop` from raster package to cut down to our desired plot only. Expects first argument is raster to be clipped, and second object specifies extent. |
| 104 | + |
| 105 | +```{r, eval=FALSE} |
| 106 | +full_field_crop <- crop(full_field, site.clip) |
| 107 | +plot(full_field_crop) |
| 108 | +``` |
| 109 | + |
| 110 | +## Video 4: Calculate Greenness Index |
| 111 | + |
| 112 | +Let's say we want to calculate a greenness index for this plot. These RGB images have three bands, can see under band. `raster` reads in first band, which is red, if none is specified. |
| 113 | + |
| 114 | +We want to read in both the red and green band to do calculation. Change band argument for both raster objects. Crop them both to get just plot extent, and can plot to see that they're different. |
| 115 | + |
| 116 | + |
| 117 | +```{r, eval=FALSE} |
| 118 | +full_field |
| 119 | +
|
| 120 | +full_field_red <- raster("fullfield_L1_ua-mac_2018-05-01_rgb_stereovis_ir_sensors_fullfield_sorghum6_shade_may2018_thumb.tif", band = 1) |
| 121 | +full_field_green <- raster("fullfield_L1_ua-mac_2018-05-01_rgb_stereovis_ir_sensors_fullfield_sorghum6_shade_may2018_thumb.tif", band = 2) |
| 122 | +
|
| 123 | +crop_red <- crop(full_field_red, site_clip) |
| 124 | +crop_green <- crop(full_field_green, site_clip) |
| 125 | +
|
| 126 | +plot(crop_red) |
| 127 | +plot(crop_green) |
| 128 | +``` |
| 129 | + |
| 130 | +Now that we have our clipped red and green rasters, we can calculate an index called the normalized green-red difference index. Adding two rasters, subtracting two rasters, and then dividing them. |
| 131 | + |
| 132 | +Add and subtract is between each corresponding cell of the raster, then `cellStats` does operation across all cell values. |
| 133 | + |
| 134 | +```{r, eval=FALSE} |
| 135 | +add <- crop_red + crop_green |
| 136 | +numerator <- cellStats(add, stat = "sum") |
| 137 | +subtract <- crop_red - crop_green |
| 138 | +denominator <- cellStats(subtract, stat = "sum") |
| 139 | +greenness <- numerator / denominator |
| 140 | +``` |
| 141 | + |
| 142 | +## Video 5: Repeat Calculation on Multiple Files |
| 143 | + |
| 144 | +So now we have a greenness value for this plot for a single date in May. We can repeat this for several dates across the month, in a more efficient way. |
| 145 | +First I set up an object containing the copy and paste values (URL and file name) from each Clowder file of interest. Did for four dates across the month. |
| 146 | + |
| 147 | +Each file is a dictionary with id and filename, and they're put into the list `files`. |
| 148 | + |
| 149 | +```{python, eval=FALSE} |
| 150 | +files = [{"id": "5c81a03e4f0c0ca8052b2635?dataset=5c81709a4f0c78f6486d686c&space=", |
| 151 | +"filename": "fullfield_L1_ua-mac_2018-05-01_rgb_stereovis_ir_sensors_fullfield_sorghum6_shade_may2018_thumb.tif"}, |
| 152 | +{"id": "5c81a0314f0c78f6486d68ef?dataset=5c81709a4f0c78f6486d686c&space=", |
| 153 | +"filename": "ullfield_L1_ua-mac_2018-05-02_rgb_stereovis_ir_sensors_fullfield_sorghum6_shade_may2018_thumb.tif"}, |
| 154 | +{"id": "5c819ffd4f0c0ca8052b25c0?dataset=5c81709a4f0c78f6486d686c&space=", |
| 155 | +"filename": "ullfield_L1_ua-mac_2018-05-18_rgb_stereovis_ir_sensors_fullfield_sorghum6_sun_may2018_thumb.tif"}, |
| 156 | +{"id": "5c819fe04f0c82bd931b4b5d?dataset=5c81709a4f0c78f6486d686c&space=", |
| 157 | +"filename": "fullfield_L1_ua-mac_2018-05-28_rgb_stereovis_ir_sensors_plots_sorghum6_shade_rgb_eastedge_mn_thumb.tif"}] |
| 158 | +``` |
| 159 | + |
| 160 | +Connect to URL with requests and download with `open` like before, all in a big for loop for each of the files. |
| 161 | + |
| 162 | +This will take a minute because it's doing what we did before for three files. |
| 163 | + |
| 164 | +```{python, eval=FALSE} |
| 165 | +for file in files: |
| 166 | + file_request = requests.get('https://terraref.ncsa.illinois.edu/clowder/api/files/' + file["id"], api_key) |
| 167 | + with open(file["filename"], 'wb') as object: |
| 168 | + for chunk in file_request.iter_content(chunk_size=2014): |
| 169 | + if chunk: |
| 170 | + object.write(chunk) |
| 171 | +``` |
| 172 | + |
| 173 | +Now that we have all our files, we want to calculate greenness for each of them. To avoid having to type everything we did before over and over again, will create function to read in each file (two bands), clip them, and calculate value. |
| 174 | + |
| 175 | +```{r, eval=FALSE} |
| 176 | +get_greenness <- function(file_name, clip_coords){ |
| 177 | + band_red <- raster(file_name, band = 1) |
| 178 | + crop_red <- crop(band_red, clip_coords) |
| 179 | + band_green <- raster(file_name, band = 2) |
| 180 | + crop_green <- crop(band_green, clip_coords) |
| 181 | + add <- crop_red + crop_green |
| 182 | + numerator <- cellStats(add, stat = "sum") |
| 183 | + subtract <- crop_red - crop_green |
| 184 | + denominator <- cellStats(subtract, stat = "sum") |
| 185 | + greenness <- numerator / denominator |
| 186 | + return(greenness) |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +We want to put in, as `file_name`, a list of our files. Use `list.files` to do that, and a wildcard to specify we want all files in current directory that are geotiffs. |
| 191 | + |
| 192 | +```{r, eval=FALSE} |
| 193 | +image_file_names <- list.files(".", pattern = "*.tif") |
| 194 | +``` |
| 195 | + |
| 196 | +Then run greenness function across all files in that list, using previous plot vector to clip. Last argument is to just not include the names of the files in results. Get four greenness values as expected. |
| 197 | + |
| 198 | +```{r, eval=FALSE} |
| 199 | +greennesses <- sapply(image_file_names, get_greenness, site_clip, USE.NAMES = FALSE) |
| 200 | +``` |
| 201 | + |
| 202 | +We want to put these values into a dataframe with another column, dates, so we can later combine this with other data based on day. Just writing out each date by hand, but there's programmatic ways using regex to pull date values out of file names. |
| 203 | + |
| 204 | +Need to turn the date values into date objects like we've done before so they can be combined and plotted more easily. Create new column with dplyr. |
| 205 | + |
| 206 | +```{r, eval=FALSE} |
| 207 | +greenness_df <- data.frame(date = c('2018-05-01', '2018-05-02', '2018-05-18', '2018-05-28'), greenness = greennesses) |
| 208 | +
|
| 209 | +library(dplyr) |
| 210 | +greenness_df <- greenness_df %>% |
| 211 | + mutate(day = as.Date(date)) |
| 212 | +``` |
| 213 | + |
| 214 | +## Video 6: Compare Greenness to Trait Value |
| 215 | + |
| 216 | +What we want to do is compare a trait value, canopy cover, to this greenness metric. We can pull in canopy cover values with `traits` package like we did the first webinar. The options have already been set. Also need to turn date into date object. |
| 217 | + |
| 218 | +```{r, eval=FALSE} |
| 219 | +cover <- betydb_query(trait = "canopy_cover", |
| 220 | + date = "~2018 May", |
| 221 | + limit = "none") |
| 222 | +
|
| 223 | +cover_day <- cover %>% |
| 224 | + mutate(day = as.Date(raw_date)) |
| 225 | +``` |
| 226 | + |
| 227 | +Because there are multiple canopy cover values across the entire field, we're going to get the mean and average for each day. Using `dplyr`, group by day and then summarize across those. |
| 228 | + |
| 229 | +```{r, eval=FALSE} |
| 230 | +cover_daily <- cover_day %>% |
| 231 | + group_by(day) %>% |
| 232 | + summarize(mean_cover = mean(mean)) |
| 233 | +``` |
| 234 | + |
| 235 | +Then combine these with the greenness values by day. Doing left join because we only want to keep the dates in the greenness dataframe, there are more in the canopy cover one. |
| 236 | + |
| 237 | +```{r, eval=FALSE} |
| 238 | +combined_values <- left_join(greenness_df, cover_daily, by = "day") |
| 239 | +``` |
| 240 | + |
| 241 | +Plot to see if there's any relationship between the two. |
| 242 | + |
| 243 | +```{r, eval=FALSE} |
| 244 | +library(ggplot2) |
| 245 | +ggplot(combined_values, aes(x = mean_cover, y = greenness)) + |
| 246 | + geom_point() |
| 247 | +``` |
| 248 | + |
| 249 | +Not much of a relationship. What could be done better here? |
| 250 | + |
| 251 | +* Do for more dates |
| 252 | +* Use canopy cover from specific plot only |
| 253 | +* Do for more plots |
0 commit comments