#' RSEEsmooth - performs LOWESS and RRM
#' @param object data frame with x and y coordinates.
#' name of x coordinates vector should contain "x" or "X"
#' column name of y coordinates vector should contain "y" or "Y"
#' @param path if object is NULL, loads a CSV file of XY coordinates
#' @param project project name
#' @param individual name of individual animal
#' @param forced set to TRUE if no fixed homebase, and FALSE if fixed homebase (free exploration)
#' @param fps frames per second (of the tracked video)
# LOWESS arguments:
#' @param h.loess half window width of LOWESS
#' @param d.loess degree of polynomial (only d=2 implemented for velocities and accelerations)
#' @param r.loess number of LOWESS iterations (recommended: 2)
# RRM arguments:
#' @param frames.rrm RRM arguments (see NOTE)
#' @param cutoff.rrm RRM arguments (see NOTE)
#' @param h.rrm sequence of half-window widths for repeated RM
#' @note an arrest is a sequence of at least "frames.rrm" frames in which the maximum distance between the two farthest points (after RRM) is no more than "cutoff.rrm"
# VALUES:
#' @return The function returns a list with following objects:
#' \describe{
#' \item{smooth.data}{ data frame, containing for each frame: smooth x and y coordinates,
#' smooth velocities and accelerations in x and y, and entry.}
#' \item{speed.acc}{data frame of smooth point speeds and accelerations}
#' \item{entry}{data frame of start and end frames for each entry (only 1 entry if forced = T)}
#' \item{motion.segments}{data frame of start and end frames for each arrest}
#' \describe{info - list of general information about the session:
#' \item{project, individual}{input names}
#' \item{session.length.minutes}{length of the session in minutes}
#' \item{home}{fixed homebase coordinates (if forced = F)}
#' \item{number.of.entries}{more than 1 if forced = F}
#' }
#' }
#' @export
RSEEsmooth <- function(object=NULL,path=NULL,project = "",individual = "",
forced=T,fps=25,h.loess=10,d.loess=2,r.loess=2,
frames.rrm=5,cutoff.rrm=1e-6,h.rrm=c(7,5,3,3)) {
options(warn=-1)
library(utils)
# loading track data and fixing NAs
if (is.null(object)) {
if (is.null(path)) { stop("No object or path")
} else object <- read.csv(path)
}
which.x <- union(grep("X",names(object)),grep("x",names(object)))
which.y <- union(grep("Y",names(object)),grep("y",names(object)))
if ((length(which.x)==0)||(length(which.y)==0)) stop("No X and Y coordinates")
cat("RSEE path smoother:\n",sep="")
cat(" Preparing data and Fixing NAs... ",sep="")
A <- data.frame(x=as.numeric(as.character(object[,which.x])),
y=as.numeric(as.character(object[,which.y])))
rm(object)
if (!forced) { home <- find.home(A$x,A$y)
} else home <- NULL
A <- fix.na(A,home)
n <- nrow(A)
x <- rep(0,n)
y <- rep(0,n)
v.x <- rep(0,n)
v.y <- rep(0,n)
a.x <- rep(0,n)
a.y <- rep(0,n)
# find entries
if (!forced) {
ind.entry <- which((A$x!=home[1])|(A$y!=home[2]))
df <- diff(ind.entry)
ent.start <- ind.entry[which(c(2,df)>1)]
ent.stop <- ind.entry[which(c(df,2)>1)]
rm(df)
} else {
ind.entry <- 1:n
ent.start <- 1
ent.stop <- n
}
n.ent <- length(ent.start)
which.entry <- rep(0,n)
for (i in 1:n.ent) which.entry[ent.start[i]:ent.stop[i]] <- i
cat("Done.\n Running LOESS... ",sep="")
if (n.ent>1) pb <- txtProgressBar(min = 1, max = n.ent, initial = 1, char = "=",style=3)
# Performing LOESS separately for each entry
for (j in 1:length(ent.start)) {
ind <- ent.start[j]:ent.stop[j]
temp.x <- LOWESS(A$x[ind],fps,h.loess,d.loess,r.loess)
x[ind] <- temp.x[ ,'loc']
v.x[ind] <- temp.x[ ,'v']
a.x[ind] <- temp.x[ ,'a']
temp.y <- LOWESS(A$y[ind],fps,h.loess,d.loess,r.loess)
y[ind] <- temp.y[ ,'loc']
v.y[ind] <- temp.y[ ,'v']
a.y[ind] <- temp.y[ ,'a']
rm(temp.x,temp.y,ind)
if (n.ent>1) setTxtProgressBar(pb, j)
}
# Performing RRM separately for each entry
# arrests segments are added
cat("Done.\n Running RRM, finding arrests and motion segments... ")
arr.start <- numeric() # start frames of arrests
arr.stop <- numeric() # end frames of arrests
arr.entry <- numeric() # in which entry the arrests was performed
if (n.ent>1) pb <- txtProgressBar(min = 1, max = n.ent, initial = 1, char = "=",style=3)
for (j in 1:length(ent.start)) {
ind <- ent.start[j]:ent.stop[j]
rrm <- RRM(A$x[ind],A$y[ind],l=frames.rrm,eps=cutoff.rrm,h.seq=h.rrm)
arr.start <- c(arr.start,ind[rrm$start])
arr.stop <- c(arr.stop,ind[rrm$stop])
arr.entry <- c(arr.entry,rep(j,length(rrm$start)))
rm(rrm,ind)
if (n.ent>1) setTxtProgressBar(pb, j)
}
# set velocity and acceleration to zero in arrests
# forces X and Y coordinates on a straight line
for (j in 1:length(arr.start)) {
ind <- arr.start[j]:arr.stop[j]
v.x[ind] <- 0
a.x[ind] <- 0
x[ind] <- straight.line(x[ind])
v.y[ind] <- 0
a.y[ind] <- 0
y[ind] <- straight.line(y[ind])
}
v <- sqrt(v.x^2+v.y^2)
# find motion segments
motion.start <- numeric() # start frames of motion segments
motion.stop <- numeric() # end frames of motion segments
motion.entry <- numeric() # which entry the motion segment
max.v <- numeric()
n.arr <- length(arr.entry)
count <- 0
flag <- 1
for (j in 1:length(ent.start)) {
ind <- ent.start[j]:ent.stop[j]
count <- count + flag
flag <- 1
w.m <- c(count:n.arr)[which(arr.entry[count:n.arr]==j)]
if ((length(w.m)>0)&&(count<=n.arr)) {
w <- max(w.m)
if (arr.start[count]>ent.start[j]) {
motion.start <- c(motion.start,ent.start[j])
motion.stop <- c(motion.stop,arr.start[count]-1)
motion.entry <- c(motion.entry,j)
max.v <- c(max.v,max(v[ent.start[j]:(arr.start[count]-1)]))
}
if (w>count) {
for (k in count:(w-1)) {
motion.start <- c(motion.start,arr.stop[k]+1)
motion.stop <- c(motion.stop,arr.start[k+1]-1)
motion.entry <- c(motion.entry,j)
max.v <- c(max.v,max(v[(arr.stop[k]+1):(arr.start[k+1]-1)]))
}
}
if (arr.stop[w]<ent.stop[j]) {
motion.start <- c(motion.start,arr.stop[w]+1)
motion.stop <- c(motion.stop,ent.stop[j])
motion.entry <- c(motion.entry,j)
max.v <- c(max.v,max(v[(arr.stop[w]+1):ent.stop[j]]))
}
count <- w
} else {
flag <- 0
motion.start <- c(motion.start,ent.start[j])
motion.stop <- c(motion.stop,ent.stop[j])
motion.entry <- c(motion.entry,j)
max.v <- c(max.v,max(v[ind]))
}
rm(ind)
}
# Original smoothing file (other are changed later on)
smooth.data <- data.frame(x=x,v.x=v.x,a.x=a.x,
y=y,v.y=v.y,a.y=a.y,entry = which.entry)
spd.acc <- data.frame(v = v, a = sqrt(a.x^2+a.y^2))
# entry data
entry.dat <- data.frame(start=ent.start,end=ent.stop,length=ent.stop-ent.start+1)
# data frame of arrests data
arr.data <- data.frame(start=arr.start,end=arr.stop,length=arr.stop-arr.start+1,entry=arr.entry)
# data frame of motions segements data
motion.dat <- data.frame(start=motion.start,end=motion.stop,
length=motion.stop-motion.start+1,entry=motion.entry,max.v=max.v)
# info
info <- list(project = project, individual = individual,
length.of.session.minutes = n/(fps*60),home = home,number.of.entries = n.ent)
rm(A,x,y,v.x,v.y,a.x,a.y,v,
ent.start,ent.stop,ind.entry,
arr.stop,arr.start,arr.entry,
motion.start,motion.stop,motion.entry,max.v,
n.arr,count,home)
gc()
cat("Done.\nComplete.",sep="")
return(list(smooth.data = smooth.data,
speed.acc = spd.acc,
entry = entry.dat,
arrests = arr.data,
motion.segments = motion.dat,
info = info))
}
#' Locally Weighted Scatterplot Smoothing
#' @description Applies LOWESS on select vector as a function of time
#' @param - fps, frame per second
#' @param h - Half window size, the window size is 2 * h + 1
#' @param d - Degree of polynomial
#' @param r - Number of repeated iterations
#' @param smooth.para - Smoothing parameter, defines
#'
#' @return loc - the smoothed location
#' @return v - velocity obtained by deriving the polynomial at the specific point
#' @return a - accelration, obtained by deriving the polynomial at the specific point twice
LOWESS <- function(x, fps = 25, h = 10, d = 2, r = 2, smooth.para = 6) {
n <- length(x)
if (h > n) {
h <- n
warning('Window size cannot be smaller than number of observation, coercing h = n')
}
time.vec <- seq(-h , h , 1) / fps ### For weighting and derivatives
### Creating weight
w <- (1 - abs( seq(-h, h, 1) / h) ** 3) ** 3 ### Tricube weight (distance on X axis)
w <- t(replicate(n, w)) ### Creating weight vector for each observation
local.test.mat <- NULL
for (i in 0:d) {
local.test.mat <- cbind(local.test.mat, (time.vec) ** i)
}
### Creating local regression X vector
### Padding with zeros for start
for (i in 1:h) {
w[i, seq(1 , h - i + 1, 1)] <- 0
}
### Padding with zeros for end
for (i in (n - h + 1):n) {
w[i, seq(h + (n - i) + 2, 2 * h + 1)] <- 0
}
### Creating residual weighting
delta <- matrix(1, n, 2 * h + 1) # weights due to distance from the fitted curve
### Padding X with zeros
padx <- c(rep(0, h), x, rep(0, h))
#### Iterating
for (i in 1:r) {
if (i < r) {
delta <- iterLowess(weightX = w, weightY = delta, modelmat = local.test.mat, x = padx, h = h, smoothpara = smooth.para)
}
if (i == r) { ### Last iteration (return movment properties)
mov.mat <- lastIterLowess(weightX = w, weightY = delta, modelmat = local.test.mat, x = padx, h = h)
}
}
colnames(mov.mat)[1:(d + 1)] <- c('loc', 'v', 'a', rep(NA, max(d - 2, 0)))[1:(d + 1)]
if (d > 1) {
mov.mat[ ,3] <- mov.mat[ ,3] * 2 ## Accelration
}
return(mov.mat)
}
#' Repeated running medians
#'
#' @param x - Raw x tracked coordinates
#' @param y - Raw y tracked coordinates
#' @param l - Minimum number of frames to be considered as arrest
#' @param eps - Cutoff value for determining arrests (maximum distance allowed to travel during arrest)
#'
#' @return Table of specfying beginning and ends of arrests
#'
RRM <- function(x,y,l=5,eps=1e-6,h.seq=c(7,5,3,3)) {
n <- length(x)
if (n<l) return(data.frame(start=numeric(),stop=numeric()))
if (max(h.seq)>n) h.seq[which(h.seq>n)] <- n
x.hat <- x
y.hat <- y
for (h in h.seq) {
x.hat <- runmed(x.hat, 2 * h + 1, endrule = 'constant')
y.hat <- runmed(y.hat, 2 * h + 1, endrule = 'constant')
}
x <- x.hat
y <- y.hat
arrests.start <- numeric()
arrests.stop <- numeric()
i <- 1
while (i < n) {
k <- i+1
flag <- T
while (flag) {
df <- sqrt((x[k]-x[i])^2+(y[k]-y[i])^2)
if (df>eps) {
flag <- F
k <- k-1
} else {
if (k == n) {
flag <- F
}
else k <- k+1
}
}
if (k- i + 1 > l) {
arrests.start <- c(arrests.start,i)
arrests.stop <- c(arrests.stop,k)
}
i <- k + 2
}
#df <- rep(0,n)
#if (l>1) {
# for (i in 2:l) {
# df <- cbind(df,c(sqrt((x[i:n]-x[1:(n-i+1)])^2+(y[i:n]-y[1:(n-i+1)])^2),rep(0,i-1)))
# }
#}
return(data.frame('start' = arrests.start,'stop' = arrests.stop)) ## Might cause error, if so, switch to list
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.