#' @name voach
#' @title Calculate the Vowel Overlap Assessment with Convex Hulls method
#'
#' This is an implementation of the Vowel Overlap Assessment with Convex Hulls metric (VOACH)
#' by Haynes and Taylor (2014) in the R programming language. This program calculates convex
#' hulls that fit the data passed in, and then fits another hull around the overlapping
#' region of the previously found hulls (if possible). It then uses this overlap hull to find
#' the largest percentage of overlap.
#'
#' @author Matthew C. Kelley
#' @import geometry
#' @import stats
#'
#' @param vowel A vector of vowel labels
#' @param f1 A vector F1 values
#' @param f2 The A vector of F2 values
#' @param dur A vector of duration values; if vector not passed in, 2D
#' version of function will be run
#'
#' @details
#'
#' The function can accept either a character vector or a factor vector for the
#' \code{vowel} labels, as the vector will be coerced to a factor vector in the
#' process of executing the code.
#'
#' Values for formants must be integers or numerics. This function does not provide
#' normalization capabilities, so data should be normalized before running the
#' function.
#'
#' @return An S3 object of class \code{soam} with the following attributes:
#' \item{overlap}{The calculated maximum overlap.}
#' \item{F1}{The F1 data passed into the funciton.}
#' \item{F2}{The F2 data passed into the function.}
#' \item{Dur}{The duration data passed into the function. Values set to
#' \code{NA} if duration not passed in}
#' \item{vowel}{The vowel data passed into the function.}
#' \item{hulls}{List of convex hulls calculated for the measure.}
#' \item{overlap_pts}{The points that were contained in both hulls}
#' \item{vowels}{List where each element is a vowel category}
#' \item{analysis}{"2D" if the 2D analysis was run. "3D" if the 3D analysis was run.}
#'
#' @references
#'
#' Haynes, E. F., & Taylor, M. (2014). An assessment of acoustic contrast between
#' long and short vowels using convex hulls. \emph{The Journal of the Acoustical Society of
#' America}, 136(2), 883–891.
#'
#' Hillenbrand, J. et al. 1995. Acoustic characteristics of American English vowels.
#' \emph{Journal of the Acoustical Society of America} 97(5), 3099-3111.
#'
#' @export
library(geometry)
voach = function(vowel, f1, f2, dur=NULL) {
# Create data frame used for analysis
if(is.null(dur)) {
data_copy = data.frame(Vowel=vowel, F1=f1, F2=f2)
} else {
data_copy = data.frame(Vowel=vowel, F1=f1, F2=f2, Duration=dur)
}
vowels = list()
vowelcats = levels(as.factor(data_copy$Vowel))
numvowelcats = length(vowelcats)
analysis = ifelse(ncol(data_copy) == 3, "2D", "3D")
if(analysis == "2D") {
hulls = list() # List to hold the different convex hulls
for(i in 1:numvowelcats) {
curr_vowel = data_copy[which(data_copy$Vowel == vowelcats[i]),2:3]
vowels[[i]] = curr_vowel
}
# Fit hulls to each vowel category
for(i in 1:length(vowels)) {
hulls[[i]] = convhulln(vowels[[i]], options="FA")
}
overlap = matrix(nrow=0, ncol=2) # Matrix to hold overlapping points
# Testing if each point "point" of vowel "v" is in the hull
# "h" assoc. with vowel "to_compare"
for(i in 1:length(vowels)) {
v = vowels[[i]]
for(j in 1:nrow(v)) {
point = v[j,]
overlapping = T
for(k in 1:length(hulls)) {
if(k == i) next # would be comparing to hull it comes from
h = hulls[[k]]
hull_pts = h$hull
to_compare = vowels[[k]]
temp_v = rbind(to_compare, point)
temp_h = convhulln(temp_v, options="FA")
if(!identical(temp_h$hull, h$hull)) { # Not in all hulls, so stop search now
overlapping = FALSE
break
}
}
if(overlapping) overlap = rbind(overlap, point)
}
}
h_overlap = NULL
# Check if enough points to make a hull (at least 3 necessary)
if(nrow(overlap) > 2) {
tryCatch({
h_overlap = convhulln(overlap, options="FA")
},
error = function(e) {
h_overlap = NULL
})
}
} else {
hulls = list()
for(i in 1:numvowelcats) {
curr_vowel = data_copy[which(data_copy$Vowel == vowelcats[i]),2:4]
vowels[[i]] = curr_vowel
}
# Fit hulls to each vowel category
for(i in 1:length(vowels)) {
hulls[[i]] = convhulln(vowels[[i]], options="FA")
}
overlap = matrix(nrow=0, ncol=3) # Matrix to hold overlapping points
for(i in 1:length(vowels)) {
v = vowels[[i]]
for(j in 1:nrow(v)) {
point = v[j,]
overlapping = T
for(k in 1:length(hulls)) {
if(k == i) next # would be comparing to hull it comes from
h = hulls[[k]]
hull_pts = h$hull
to_compare = vowels[[k]]
temp_v = rbind(to_compare, point)
temp_h = convhulln(temp_v, options="FA")
if(!identical(temp_h$hull, h$hull)) { # Not in all hulls, so stop search now
overlapping = FALSE
break
}
}
if(overlapping) overlap = rbind(overlap, point)
}
}
}
h_overlap = NULL
# Check if enough unique points to make a hull (at least 3 necessary)
n_unique_points = nrow(overlap[!duplicated(overlap),])
if(((analysis == "2d" || analysis == "2D") && n_unique_points > 2) || ((analysis == "3d" || analysis == "3D") && n_unique_points > 3)){
tryCatch({
h_overlap = convhulln(overlap, options="FA")
},
error = function(e) {
warning("Convex hull of overlap couldn't be created from the data, though there are points overlapping. Returning overlap value of 0")
h_overlap = NULL
})
}
areas = vector(length=length(hulls)) # Vector to hold hulls' areas
for(i in 1:length(hulls)) {
areas[i] = hulls[[i]]$vol
}
# Find largest overlap percentage
if(!is.null(h_overlap)) {
omega = max(h_overlap$vol/areas)
} else {
omega = 0
}
ret = list(overlap=omega, F1=data_copy$F1, F2=data_copy$F2, Dur=ifelse(ncol(data_copy) == 5, data_copy$Duration, rep(NA, nrow(data_copy))), vowel=data_copy$Vowel, hulls=hulls, overlap_pts=overlap, vowels=vowels, analysis=analysis)
class(ret) = "voach"
return(ret)
}