Skip to content

Commit

Permalink
Merge pull request #64 from fishR-Core-Team/43-allow-user-to-quickly-…
Browse files Browse the repository at this point in the history
…review-digitized-images-across-multiple-individuals

43 allow user to quickly review digitized images across multiple individuals
  • Loading branch information
droglenc committed Dec 22, 2023
2 parents 3ab3b24 + be96c8e commit c284097
Show file tree
Hide file tree
Showing 14 changed files with 389 additions and 94 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export(findScalingFactor)
export(gConvert)
export(getID)
export(listFiles)
export(reviewDigitizedImages)
export(saveDigitizedImage)
export(showDigitizedImage)
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# RFishBC 0.2.7.9000
* `iCheckFiles()`: Added to provide simplicity of use with `reviewDigitized Images()`, `saveDigitizedImage()`, and `showDigitizedImage()`.
* `iHndlFilenames()`: Used `sringr::str_wrap()` to better handle a long error message.
* `reviewDigitizedImages()`: Added (this address [#43](https://github.com/fishR-Core-Team/RFishBC/issues/43).
* `saveDigitizedImage()`: Implemented `iCheckFiles()`. Removed file based tests and included them in tests for `iCheckFiles()`.
* `showDigitizedImage()`: Added `Encoding()` in another spot for unicode "arrows" when plotting (see news for v0.2.7). Implemented `iCheckFiles()`. Adjusted the method for checking whether two files used the same underlying image. Moved the recycling of `pch.show=` *et al* to outside of the loop (so as not to repeat the same recycling for each iteration of the loop). Removed file based tests and included them in tests for `iCheckFiles()`.

# RFishBC 0.2.7
* Updated my (DHO) e-mail address in description and all `@author` tags in the documentation files.
Expand Down
41 changes: 37 additions & 4 deletions R/RFishBC-internals.R
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,49 @@ iHndlFilenames <- function(nm,filter,multi=TRUE) {
dn <- dirname(nm[1])
wd <- getwd()
if (!dn %in% c(".",wd)) {
STOP("The file is in ",normalizePath(dn),", which is NOT\n",
" the current working directory of ",normalizePath(wd),".\n",
" Please use 'setwd()' to change the working directory\n",
" and then try the function again.")
tmp <- paste0("The file is in ",normalizePath(dn),
", which is NOT the current working directory of ",
normalizePath(wd),
". Please use 'setwd()' to change the working directory ",
"and then try the function again.")
STOP(stringr::str_wrap(tmp,exdent=2))
}
#### Make sure just the filenames (no path info) is returned
basename(nm)
}


########################################################################
## Checks to make sure that all files in a list of files come from
## digitizeRadii(). Those that do not are removed from the list. An
## error is thrown if the list is empty after all have been checked.
########################################################################
iCheckFiles <- function(nms,showWarnings=TRUE) {
i2drop <- NULL
for (i in seq_along(nms)) {
### Make sure each file is an RData file from digitizeRadii()
if (!isRData(nms[i])) {
tmp <- paste(nms[i],
"is not an RData file saved from 'digitizeRadii().",
"It will be dropped from provided list of files.")
if (showWarnings) WARN(stringr::str_wrap(tmp,exdent=2))
i2drop <- c(i2drop,i)
} else {
dat <- readRDS(nms[i])
if (!inherits(dat,"RFishBC")) {
tmp <- paste(nms[i],
"does not appear to be from 'digitizeRadii().",
"It will be dropped from provided list of files.")
if (showWarnings) WARN(stringr::str_wrap(tmp,exdent=2))
i2drop <- c(i2drop,i)
}
}
}
## Return modified list of filenames
if (!is.null(i2drop)) nms <- nms[-i2drop]
if (length(nms)==0) STOP("There are no files left in the provided list.")
else invisible(nms)
}

########################################################################
## Load and displays a structure image.
Expand Down
129 changes: 129 additions & 0 deletions R/reviewDigitizedImages.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#' @title Allows user to efficiently view images with selected points saved in multiple R data files.
#'
#' @description Allows user to efficiently view images with points to represent annuli that were saved to multiple R data files using \code{\link{digitizeRadii}}. The user can press keyboard buttons to move between (forward and backward) images.
#'
#' @param nms A string (or vector of strings) that indicates the R data file(s) created with \code{\link{digitizeRadii}}. If missing the user will be provided a dialog box from which to choose the file(s). The file(s) must be in the current working directory (see \code{\link{getwd}} result). May also be a single \code{RFishBC} object created with \code{\link{digitizeRadii}}.
#' @param deviceType See details in \code{\link{RFBCoptions}}.
#' @param pch.show See details in \code{\link{RFBCoptions}}.
#' @param col.show See details in \code{\link{RFBCoptions}}.
#' @param cex.show See details in \code{\link{RFBCoptions}}.
#' @param connect See details in \code{\link{RFBCoptions}}.
#' @param col.connect See details in \code{\link{RFBCoptions}}.
#' @param lwd.connect See details in \code{\link{RFBCoptions}}.
#' @param col.scaleBar See details in \code{\link{RFBCoptions}}.
#' @param lwd.scaleBar See details in \code{\link{RFBCoptions}}.
#' @param showScaleBarLength See details in \code{\link{RFBCoptions}}.
#' @param cex.scaleBar See details in \code{\link{RFBCoptions}}.
#' @param showAnnuliLabels See details in \code{\link{RFBCoptions}}.
#' @param annuliLabels See details in \code{\link{RFBCoptions}}.
#' @param col.ann See details in \code{\link{RFBCoptions}}.
#' @param cex.ann See details in \code{\link{RFBCoptions}}.
#' @param offset.ann See details in \code{\link{RFBCoptions}}.
#'
#' @return None.
#'
#' @details Multiple images with marked annuli recorded from \code{\link{digitizeRadii}} and saved in an RData file are viewed efficiently with this function. Appropriate RData files in the current working directory can be given as a vector (see \code{\link{listFiles}}) to \code{nms}. The image from the first first file will be displayed in a stand-along window. The image in the next file can be viewed by pressing the \sQuote{n} or \sQuote{>} keys. Previous images can be returned to by pressing the \sQuote{p} or \sQuote{<} keys. Press the \sQuote{f} or \sQuote{d} key to close the window when done reviewing the images.
#'
#' @seealso \code{\link{showDigitizedImage}}, \code{\link{saveDigitizedImage}}, \code{\link{digitizeRadii}}, \code{\link{RFBCoptions}}, and \code{\link{jpeg}}, \code{\link{png}}, and \code{\link{pdf}}.
#'
#' @author Derek H. Ogle, \email{[email protected]}
#'
#' @export
#'
#' @examples
#' ## None because this requires interaction from the user.
#' ## See the link to the extensive documentation in the Details.
#'
reviewDigitizedImages <- function(nms,deviceType,
pch.show,col.show,cex.show,
connect,col.connect,lwd.connect,
col.scaleBar,lwd.scaleBar,
showScaleBarLength,cex.scaleBar,
showAnnuliLabels,annuliLabels,
col.ann,cex.ann,offset.ann) {

## Internal function for handling key press event
keyPress <- function(key) ifelse(key %in% c("f","q"),"DONE",
ifelse(key %in% c("n","Right","Up"),"Next",
ifelse(key %in% c("p","Left","Down"),"Prev",
"NONE")))

## Internal function for showing image
iReviewDigitizedImage <- function(nm,msg1,deviceType,
pch.show,col.show,cex.show,
connect,col.connect,lwd.connect,
col.scaleBar,lwd.scaleBar,
showScaleBarLength,cex.scaleBar,
showAnnuliLabels,annuliLabels,
col.ann,cex.ann,offset.ann) {
cat("Showing ",msg1,".",sep="")
showDigitizedImage(nm,deviceType,
pch.show,col.show,cex.show,
connect,col.connect,lwd.connect,
col.scaleBar,lwd.scaleBar,
showScaleBarLength,cex.scaleBar,
showAnnuliLabels,annuliLabels,
col.ann,cex.ann,offset.ann)
}

# MAIN FUNCTION
## Get image file names ######################################################
## If nms is missing then allow the user to choose a file or files
if (missing(nms)) nms <- iHndlFilenames(nms,filter="RData",multi=TRUE) # nocov start
## If nms is an RFishBC object (and not a filename) then extract the
## filename otherwise process the filename(s)
if (inherits(nms,"RFishBC")) nms <- nms$datanm
else nms <- iHndlFilenames(nms,filter="RData",multi=TRUE)

## Make sure files are from digitizeRadii
nms <- iCheckFiles(nms)

## Setup for the loop ########################################################
num_imgs <- length(nms)
i <- 0
act <- "Next"
msg2 <- ": 'f'/'q'=DONE, 'n'/'>'=Next, 'p'/'<'=Previous"

## Access images until users says "Done" #####################################
### The use if iReviewDigitizedImage() looks redundant below, but doing this
### means that the image will not be redisplayed when trying to go to next
### or previous when next or previous does not exist.
while (act!="DONE") {
if (act=="Next") {
if (i<num_imgs) {
i <- i+1
msg1 <- tools::file_path_sans_ext(nms[i])
iReviewDigitizedImage(nms[i],msg1,deviceType,
pch.show,col.show,cex.show,
connect,col.connect,lwd.connect,
col.scaleBar,lwd.scaleBar,
showScaleBarLength,cex.scaleBar,
showAnnuliLabels,annuliLabels,
col.ann,cex.ann,offset.ann)
} else {
cat("There is no 'Next' image. ")
i <- i
}
} else if (act=="Prev") {
if (i>1) {
i <- i-1
msg1 <- tools::file_path_sans_ext(nms[i])
iReviewDigitizedImage(nms[i],msg1,deviceType,
pch.show,col.show,cex.show,
connect,col.connect,lwd.connect,
col.scaleBar,lwd.scaleBar,
showScaleBarLength,cex.scaleBar,
showAnnuliLabels,annuliLabels,
col.ann,cex.ann,offset.ann)
} else {
cat("There is no 'Prev'ious image. ")
i <- i
}
} else cat("Must use",msg2,sep="")
act <- grDevices::getGraphicsEvent(paste0(msg1,msg2),
consolePrompt="",
onKeybd=keyPress,onMouseDown="")
}
grDevices::dev.off()
cat("Done.") # nocov end
}
64 changes: 30 additions & 34 deletions R/saveDigitizedImage.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#'
#' @return None, but a file is created in the working directory.
#'
#' @details None.
#' @details Images with marked annuli recorded from \code{\link{digitizeRadii}} and saved in an RData file can be saved to JPEG, PNG, or PDF files with this function. Appropriate RData files in the current working directory can be given as a vector (see \code{\link{listFiles}} to \code{nms} or selected from a dialog box if the first argument is left blank. The type of output file is JPEG by default but can be changed with \code{fileType} (e.g., to \code{"png"} or \code{"pdf"}). The resultant files will be saved in the same directory with the same filename as the original RData file but with \dQuote{_marked} appended to the name (the appended string can be changed with \code{suffix}).
#'
#' @seealso \code{\link{showDigitizedImage}}, \code{\link{digitizeRadii}}, \code{\link{RFBCoptions}}, and \code{\link{jpeg}}, \code{\link{png}}, and \code{\link{pdf}}.
#'
Expand Down Expand Up @@ -54,41 +54,37 @@ saveDigitizedImage <- function(nms,fileType=c("jpeg","png","pdf"),
## filename otherwise process the filename(s)
if (inherits(nms,"RFishBC")) nms <- nms$datanm # nocov end
else nms <- iHndlFilenames(nms,filter="RData",multi=TRUE)

## Get number of readings ####################################################

## Make sure files are from digitizeRadii
nms <- iCheckFiles(nms)

## Cycle through files #######################################################
for (i in nms) {
if (!isRData(i)) {
WARN(i," is not an RData file saved from 'digitizeRadii().")
# start to make the filename
nm <- paste0(tools::file_path_sans_ext(i),suffix)
# display image ...
d <- showDigitizedImage(i,"default",
pch.show,col.show,cex.show,
connect,col.connect,lwd.connect,
col.scaleBar,lwd.scaleBar,
showScaleBarLength,cex.scaleBar,
showAnnuliLabels,annuliLabels,
col.ann,cex.ann,offset.ann)
# ... and then send to file
if (fileType=="jpeg") {
grDevices::dev.copy(grDevices::jpeg,paste0(nm,".jpg"),
width=d$windowSize[1],height=d$windowSize[2],
units="in",res=res)
} else if (fileType=="png") {
grDevices::dev.copy(grDevices::png,paste0(nm,".png"),
width=d$windowSize[1],height=d$windowSize[2],
units="in",res=res)
} else {
dat <- NULL # try to avoid "no visible binding" note
dat <- readRDS(i)
if (!inherits(dat,"RFishBC"))
WARN(i," does not appear to be from 'digitizeRadii().")
else {
d <- showDigitizedImage(dat,"default",
pch.show,col.show,cex.show,
connect,col.connect,lwd.connect,
col.scaleBar,lwd.scaleBar,
showScaleBarLength,cex.scaleBar,
showAnnuliLabels,annuliLabels,
col.ann,cex.ann,offset.ann)
if (fileType=="jpeg") {
nm <- paste0(tools::file_path_sans_ext(i),suffix,".jpg")
grDevices::dev.copy(grDevices::jpeg,nm,
width=d$windowSize[1],height=d$windowSize[2],
units="in",res=res)
} else if (fileType=="png") {
nm <- paste0(tools::file_path_sans_ext(i),suffix,".png")
grDevices::dev.copy(grDevices::png,nm,
width=d$windowSize[1],height=d$windowSize[2],
units="in",res=res)
} else {
nm <- paste0(tools::file_path_sans_ext(i),suffix,".pdf")
grDevices::dev.copy(grDevices::pdf,nm,
width=d$windowSize[1],height=d$windowSize[2])
}
grDevices::dev.off()
}
grDevices::dev.copy(grDevices::pdf,paste0(nm,".pdf"),
width=d$windowSize[1],height=d$windowSize[2])
}
grDevices::dev.off()
}
# close device if one is left open
if (!is.null(grDevices::dev.list())) invisible(grDevices::dev.off())
}
50 changes: 22 additions & 28 deletions R/showDigitizedImage.R
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,16 @@ showDigitizedImage <- function(nms,deviceType,
## filename otherwise process the filename(s)
if (inherits(nms,"RFishBC")) nms <- nms$datanm # nocov end
else nms <- iHndlFilenames(nms,filter="RData",multi=TRUE)

## Make sure files are from digitizeRadii
nms <- iCheckFiles(nms)

## Get number of readings ####################################################
num2do <- length(nms)

## Show the reading(s) ... handle one and multiple readings differently ######
if (num2do==1) {
### Make sure file is an RData file from digitizeRadii()
if (!isRData(nms))
STOP(nms," is not an RData file saved from 'digitizeRadii().")
dat <- readRDS(nms)
if (!inherits(dat,"RFishBC"))
STOP(nms," does not appear to be from 'digitizeRadii().")
img <- iShowOneDigitizedImage(dat,deviceType,
showScaleBarLength,col.scaleBar,
lwd.scaleBar,cex.scaleBar,
Expand All @@ -105,39 +103,34 @@ showDigitizedImage <- function(nms,deviceType,
showAnnuliLabels,annuliLabels,
col.ann,cex.ann,offset.ann)
} else {
tmp <- NULL
### expand pchs, colors, cexs, lwds to number of readings/transects
pch.show <- rep(pch.show,ceiling(num2do/length(pch.show)))
col.show <- rep(col.show,ceiling(num2do/length(col.show)))
cex.show <- rep(cex.show,ceiling(num2do/length(cex.show)))
col.connect <- rep(col.connect,ceiling(num2do/length(col.connect)))
lwd.connect <- rep(lwd.connect,ceiling(num2do/length(lwd.connect)))

for (i in seq_along(nms)) {
### Make sure each file is an RData file from digitizeRadii()
if (!isRData(nms[i]))
STOP(nms[i]," is not an RData file saved from 'digitizeRadii().")
dat <- readRDS(nms[i])
if (!inherits(dat,"RFishBC"))
STOP(nms[i]," does not appear to be from 'digitizeRadii().")
if (!is.null(tmp)) {
if (dat$image!=tmp) {
grDevices::dev.off()
STOP("Files appear to derive from different structure images.")
}
}
tmp <- dat$image

### expand pchs, colors, cexs, lwds to number of readings/transects
pch.show <- rep(pch.show,ceiling(num2do/length(pch.show)))
col.show <- rep(col.show,ceiling(num2do/length(col.show)))
cex.show <- rep(cex.show,ceiling(num2do/length(cex.show)))
col.connect <- rep(col.connect,ceiling(num2do/length(col.connect)))
lwd.connect <- rep(lwd.connect,ceiling(num2do/length(lwd.connect)))

### Make or add to image
if (i==1)
### Make (i=1) or add (i>1) to image
if (i==1) {
img <- iShowOneDigitizedImage(dat,deviceType,
showScaleBarLength,col.scaleBar,
lwd.scaleBar,cex.scaleBar,
connect,col.connect[i],lwd.connect[i],
useArrows,pch.show[i],col.show[i],cex.show[i],
showAnnuliLabels=FALSE,annuliLabels="",
col.ann[i],cex.ann[i],offset.ann[i])
else { # nocov start
### sav image for comparison to image in next file
tmp <- dat$image
} else { # nocov start
# if not first file, check to make sure files use same image
if (dat$image!=tmp) {
grDevices::dev.off()
STOP("Files appear to derive from different structure images.")
}

## Show connected points if asked to do so
if (connect) graphics::lines(y~x,data=dat$pts,
lwd=lwd.connect[i],col=col.connect[i],
Expand All @@ -146,6 +139,7 @@ showDigitizedImage <- function(nms,deviceType,
if (useArrows) {
pos <- iFindLabelPos(dat)
lbl <- intToUtf8(c(9650,9658,9660,9668)[pos])
lbl <- Encoding(lbl)
graphics::text(y~x,data=dat$pts[2:(nrow(dat$pts)-1),],labels=lbl,
col=col.show[i],cex=cex.show[i],pos=pos,offset=0)
if (dat$edgeIsAnnulus)
Expand Down
Loading

0 comments on commit c284097

Please sign in to comment.