Measuring fish. In my short research career this is the activity I’ve done the most. Whether measuring mosquitofish or zebrafish, the method has been:
- Photograph fish next to a scale
- Rename photo with the ID of the fish and the date
- Measure photo using ImageJ
Here I share my tricks to renaming photos using R.
Step 1: Put a single image of the fish you want to rename in a folder.We usually take two photos of each fish and choose the best one (before first copying all the photos to a back-up location, just in case). Step 2: Make the order of fish, or their names, correspond to their identity. Sometimes the fish are photographed in a pre-determined order, in which case fish names are simply assigned sequentially. More often we photograph all fish from a single tank in a random order, and then use their unique coloured markings to assign their names. In this case we needed to rename the photos to match the marking of each fish, and these file names could then be linked to the name of the fish stored in our records. |
# note that working directory is already set to our project folder (this is automatic if you use an R Project)
# importing excel document
library(readxl)
marking <- read_excel("Fish Records.xlsx", sheet = 1)
# renaming columns to remove "::" (which filemaker uses this to distinguish related table fields)
names(marking) # before
## [1] "fish_group_ID"
## [2] "individual_fish main 3::fish_indiv_ID"
## [3] "individual_fish main 3::marking"
names(marking) <- gsub(".*:", "", names(marking)) names(marking) # after
## [1] "fish_group_ID" "fish_indiv_ID" "marking"
# finding fish in the tank "DTG03", which we are renaming
# the problem is Filemaker doesn't export the repeating records for fish_group_ID, so we need to
# take the first instance of DTG03, and every subsequent NA until the next tank record
marking <- marking[which((marking$fish_group_ID == "DTG03") == T):nrow(marking),] # first record of DTG03
marking <- marking[1:(which(!is.na(marking$fish_group_ID)==T)[2]-1),] # removing everything not in DTG03
Step 4: Import the old file names, and create a corresponding vector of new file names.
old <- list.files("./original_DTG03_15_03_17") head(old, 5)
## character(0)
# Taking the markings of these fish so that they can be matched to their unique IDs
photomark <- gsub(".JPG", "", old) head(photomark, 5)
## character(0)
# Using indexing to check we're using `match` correctly
marking$marking[match(photomark, marking$marking)] == photomark # all match
## logical(0)
# saving JPEG names of new photos
new <- paste0("15_03_17_", marking$fish_indiv_ID[match(photomark, marking$marking)], ".JPG")
head(new, 5)
## [1] "15_03_17_.JPG"
Now we should have two vectors of equal length for the old and new names of photos:
length(old) == length(new)
## [1] FALSE
If this isn’t TRUE, then we need to go back and work out why. Usually one of the photos has been mislabeled, but this can also catch bigger mistakes such as missing or misplaced fish.
Step 5: Rename photos.First we will create an empty destination folder where we want the photos to go: in this case it’s called photos_to_measure. We then add the file path to the old and new file names.
oldfile <- paste0("./original_DTG03_15_03_17/", old) head(oldfile, 5)
## [1] "./original_DTG03_15_03_17/"
newfile <- paste0("./photos_to_measure/", new) head(newfile, 5)
## [1] "./photos_to_measure/15_03_17_.JPG"
file.rename(from = oldfile, to = newfile)
## [1] TRUE
Not just for fish photos!
Whenever you want to rename a bunch of files, file.rename could save you lots of time.
R is found in tRial and eRRoR
file.rename is a base function. Whenever I discover that something I’ve been doing in R for ages can be more easily done using a simple base function, I’m alternately amazed and infuriated. Before I found file.rename I renamed thousands - thousands! - of photos using a looping method that is more complicated and slower.
library(jpeg)
oldfile <- paste0("./original_DTG03_15_03_17 copy/", old)
newfile <- paste0("./photos_to_measure/", new)
for(i in 1:length(newfile)){
writeJPEG(image = readJPEG(oldfile[i]), target = newfile[i], quality = 1)
}
I don’t regret the time it took me to discover file.rename, for a couple of reasons. The loop allows you to specify the file size of the renamed image by specifying quality on a scale form 0 to 1 (see ?writeJPEG). In our database we sometimes store photos of important fish, and to keep the file size lower it’s useful to export these photos with quality = 0.3. More importantly, R is about the journey as well as the destination. That small piece of code I wrote in 2016 - which took at least an hour of trial and error! - was the first loop I ever wrote and used regularly. My inelegant code taught me a lot, and watching it rename photos was immensely satisfying. Still, we need to pick our R battles, and I hope file.rename will leave you more time for your own.