R/signal2.R

Defines functions trans2d approx2 estdim to_raster3 to_raster is_gridded coscore findpeaks_knn knnmin knnmax enhance_fun enhance_adapt enhance_hist enhance_adj warp2_fun mi warp2_trans filt2_fun filt2_guide filt2_diff filt2_adapt filt2_bi filt2_gauss filt2_conv filt2_ma

Documented in approx2 coscore enhance_adapt enhance_adj enhance_hist estdim filt2_adapt filt2_bi filt2_conv filt2_diff filt2_gauss filt2_guide filt2_ma findpeaks_knn is_gridded knnmax knnmin mi to_raster to_raster3 trans2d warp2_trans

#### 2D Filtering and Smoothing ####
## -----------------------------

filt2_ma <- function(x, width = 5L)
{
	if ( !is.matrix(x) && length(dim(x)) != 3L )
		matter_error("x must be 2D matrix or 3D array")
	if ( width %% 2L != 1L )
		width <- 1 + 2 * (width %/% 2)
	.Call(C_meanFilter2, x, width, PACKAGE="matter")
}

filt2_conv <- function(x, weights)
{
	if ( !is.matrix(x) && length(dim(x)) != 3L )
		matter_error("x must be 2D matrix or 3D array")
	if ( !is.matrix(weights) )
		matter_error("weights must be a matrix")
	.Call(C_linearFilter2, x, weights, PACKAGE="matter")
}

filt2_gauss <- function(x, width = 5L, sd = (width %/% 2) / 2)
{
	if ( !is.matrix(x) && length(dim(x)) != 3L )
		matter_error("x must be 2D matrix or 3D array")
	if ( width %% 2L != 1L )
		width <- 1L + 2L * as.integer(width %/% 2)
	radius <- width %/% 2
	i <- seq(from=-radius, to=radius, by=1L)
	weights <- dnorm(i, sd=sd) %o% dnorm(i, sd=sd)
	.Call(C_linearFilter2, x, weights, PACKAGE="matter")
}

filt2_bi <- function(x, width = 5L, sddist = (width %/% 2) / 2,
	sdrange = mad(x, na.rm = TRUE))
{
	if ( !is.matrix(x) && length(dim(x)) != 3L )
		matter_error("x must be 2D matrix or 3D array")
	if ( width %% 2L != 1L )
		width <- 1L + 2L * as.integer(width %/% 2)
	.Call(C_bilateralFilter2, x, width,
		sddist, sdrange, NA_real_, PACKAGE="matter")
}

filt2_adapt <- function(x, width = 5L, spar = 1)
{
	if ( !is.matrix(x) && length(dim(x)) != 3L )
		matter_error("x must be 2D matrix or 3D array")
	if ( width %% 2L != 1L )
		width <- 1L + 2L * as.integer(width %/% 2)
	.Call(C_bilateralFilter2, x, width,
		NA_real_, NA_real_, spar, PACKAGE="matter")
}

filt2_diff <- function(x, niter = 3L, kappa = 50,
	rate = 0.25, method = 1L)
{
	if ( !is.matrix(x) && length(dim(x)) != 3L )
		matter_error("x must be 2D matrix or 3D array")
	if ( kappa < 1 )
		matter_warn("kappa should be > 1")
	if ( rate <= 0 || rate > 0.25 )
		matter_warn("rate should be positive and < 0.25")
	.Call(C_diffusionFilter2, x, niter,
		kappa, rate, method, PACKAGE="matter")
}

filt2_guide <- function(x, width = 5L, guide = x,
	sdreg = mad(x, na.rm = TRUE))
{
	if ( !is.matrix(x) && length(dim(x)) != 3L )
		matter_error("x must be 2D matrix or 3D array")
	if ( !is.matrix(guide) && length(dim(guide)) > 3L )
		matter_error("guide must be a matrix")
	if ( length(x) != length(guide) )
		guide <- array(rep_len(guide, length(x)), dim=dim(x))
	if ( width %% 2L != 1L )
		width <- 1L + 2L * as.integer(width %/% 2)
	if ( is.integer(x) && is.double(guide) )
		storage.mode(x) <- "double"
	if ( is.double(x) && is.integer(guide) )
		storage.mode(guide) <- "double"
	.Call(C_guidedFilter2, x, guide, width, sdreg, PACKAGE="matter")
}

filt2_fun <- function(method)
{
	if ( is.character(method) )
		method <- tolower(method)
	options <- list(
		"ma" = 			filt2_ma,
		"mean" = 		filt2_ma,
		"gauss" = 		filt2_gauss,
		"gaussian" = 	filt2_gauss,
		"bi" = 			filt2_bi,
		"bilateral" = 	filt2_bi,
		"adapt" = 		filt2_adapt,
		"adaptive" = 	filt2_adapt,
		"diff" = 		filt2_diff,
		"diffusion" = 	filt2_diff,
		"guide" = 		filt2_guide,
		"guided" = 		filt2_guide)
	options[[match.arg(method, names(options))]]
}

#### 2D Alignment and warping ####
## ---------------------------------

warp2_trans <- function(x, y, control = list(),
	trans = c("rigid", "similarity", "affine"),
	metric = c("cor", "mse", "mi"), nbins = 64L,
	scale = TRUE, dimout = dim(y))
{
	# scale x and y and remove NAs
	xs <- x
	ys <- y
	xs[is.na(xs)] <- min(x, na.rm=TRUE)
	ys[is.na(ys)] <- min(y, na.rm=TRUE)
	if ( scale ) {
		xs <- (xs - min(xs)) / max(xs)
		ys <- (ys - min(ys)) / max(ys)
	}
	# prepare metric function
	metric <- match.arg(metric)
	if ( metric == "cor" ) {
		score <- function(x, y) {
			n <- max(length(x), length(y))
			x <- rep_len(x, n)
			y <- rep_len(y, n)
			-(cor(x, y, use="complete.obs")^2)
		}
	} else if ( metric == "mse" ) {
		score <- function(x, y) {
			mean((x - y)^2, na.rm=TRUE)
		}
	} else if ( metric == "mi" ) {
		score <- function(x, y) {
			-mi(x, y, nbins)
		}
	}
	# prepare transformation
	trans <- match.arg(trans)
	if ( trans == "rigid" ) {
		init <- c(0, 0, 0)
		tform <- function(par, xi, dout) {
			rot <- par[1L]
			tl <- par[2L:3L]
			trans2d(xi, rotate=rot, translate=tl,
				dimout=dout)
		}
	} else if ( trans == "similarity" ) {
		init <- c(0, 0, 0, 1, 1)
		tform <- function(par, xi, dout) {
			rot <- par[1L]
			tl <- par[2L:3L]
			sc <- par[4L:5L]
			trans2d(xi, rotate=rot, translate=tl, scale=sc,
				dimout=dout)
		}
	} else if ( trans == "affine" ) {
		init <- rbind(diag(2), c(0, 0))
		tform <- function(par, xi, dout) {
			pmat <- matrix(par, nrow=3L, ncol=2L)
			trans2d(xi, pmat=pmat,
				dimout=dout)
		}
	}
	# find optimal warp
	fn <- function(par) {
		xt <- tform(par, xs, dim(y))
		xt[is.na(xt)] <- min(xt, na.rm=TRUE)
		score(xt, ys)
	}
	best <- optim(init, fn=fn, control=control)
	z <- tform(best$par, x, dimout)
	structure(z, optim=best, trans=trans, metric=metric)
}

mi <- function(x, y, n = 64L)
{
	if ( length(x) != length(y) ) {
		lmax <- max(length(x), length(y))
		lmin <- min(length(x), length(y))
		if ( lmax %% lmin != 0L )
			matter_error("longer object length [", lmax, "] is not ",
				"a multiple of shorter object length [", lmin, "]")
		x <- rep_len(x, lmax)
		y <- rep_len(y, lmax)
	}
	# calculate the 2D bin locations
	n = min(n, length(x) %/% 4L)
	rx <- range(x, na.rm=TRUE)
	ry <- range(y, na.rm=TRUE)
	x[is.na(x)] <- rx[1L]
	y[is.na(y)] <- ry[1L]
	nx <- ny <- floor(sqrt(n))
	xi <- seq(rx[1L], rx[2L], length.out=nx)
	yi <- seq(rx[1L], rx[2L], length.out=ny)
	# calculate the 2D histogram
	halfbw <- 0.5 * c(diff(xi[1L:2L]), diff(yi[1L:2L]))
	i <- bsearch(x, xi, tol=halfbw[1L])
	j <- bsearch(y, yi, tol=halfbw[2L])
	H <- matrix(0L, nrow=nx, ncol=ny)
	for ( k in seq_along(i) )
		H[i[k],j[k]] <- H[i[k],j[k]] + 1L
	# calculate mutual information
	pxy <- H / sum(H)
	px <- rowSums(pxy)
	py <- colSums(pxy)
	px <- matrix(px, nrow=nx, ncol=ny)
	py <- matrix(py, nrow=nx, ncol=ny, byrow=TRUE)
	nz <- pxy > 0
	sum(pxy[nz] * log2(pxy[nz] / (px[nz] * py[nz])))
}

warp2_fun <- function(method)
{
	if ( is.character(method) )
		method <- tolower(method)
	options <- list(
		"trans" = 	warp2_trans)
	options[[match.arg(method, names(options))]]
}

#### Contrast enhancement ####
## ---------------------------

enhance_adj <- function(x, frac = 0.01)
{
	if ( !is.matrix(x) && length(dim(x)) != 3L )
		matter_error("x must be 2D matrix or 3D array")
	if ( is.matrix(x) ) {
		frac <- rep_len(frac, 2L)
		min <- quantile(x, frac[1L])
		max <- quantile(x, 1 - frac[2L])
		y <- replace(x, x < min, min)
		y <- replace(y, y > max, max)
		y <- rescale_iqr(y, IQR(x, na.rm=TRUE), median(x, na.rm=TRUE))
	} else {
		y <- apply(x, 3L, enhance_adj, frac=frac)
		dim(y) <- dim(x)
	}
	y
}

enhance_hist <- function(x, nbins = 256L)
{
	if ( !is.matrix(x) && length(dim(x)) != 3L )
		matter_error("x must be 2D matrix or 3D array")
	y <- .Call(C_histEq, x, nbins, PACKAGE="matter")
	rescale_iqr(y, IQR(x, na.rm=TRUE), median(x, na.rm=TRUE))
}

enhance_adapt <- function(x, width = sqrt(nrow(x) * ncol(x)) %/% 5L,
	clip = 0.1, nbins = 256L)
{
	if ( !is.matrix(x) && length(dim(x)) != 3L )
		matter_error("x must be 2D matrix or 3D array")
	y <- .Call(C_adaptHistEq, x, width, clip, nbins, PACKAGE="matter")
	rescale_iqr(y, IQR(x, na.rm=TRUE), median(x, na.rm=TRUE))
}

enhance_fun <- function(method)
{
	if ( is.character(method) )
		method <- tolower(method)
	options <- list(
		"adj" = 		enhance_adj,
		"adjust" = 		enhance_adj,
		"hist" = 		enhance_hist,
		"histeq" = 		enhance_hist,
		"histogram" = 	enhance_hist,
		"adapt" = 		enhance_adapt,
		"adaptive" = 	enhance_adapt,
		"clahe" =	 	enhance_adapt)
	options[[match.arg(method, names(options))]]
}

#### Peak detection ####
## ---------------------

knnmax <- function(x, index, k = 5L)
{
	if ( missing(index) ) {
		if ( is.null(dim(x)) )
			return(locmax(x, width=k))
		index <- expand.grid(lapply(dim(x), seq_len))
	}
	nb <- knnsearch(index, k=k)
	y <- .Call(C_localMaximaKNN, x, nb, PACKAGE="matter")
	if ( !is.null(dim(x)) )
		dim(y) <- dim(x)
	y
}

knnmin <- function(x, index, k = 5L)
{
	if ( missing(index) ) {
		if ( is.null(dim(x)) )
			return(locmin(x, width=k))
		index <- expand.grid(lapply(dim(x), seq_len))
	}
	nb <- knnsearch(index, k=k)
	y <- .Call(C_localMaximaKNN, -x, nb, PACKAGE="matter")
	if ( !is.null(dim(x)) )
		dim(y) <- dim(x)
	y
}

findpeaks_knn <- function(x, index, k = 5L,
	snr = NULL, noise = "quant", relheight = 0.005,
	arr.ind = is.array(x), useNames = TRUE, ...)
{
	if ( missing(index) && is.array(x) )
		index <- expand.grid(lapply(dim(x), seq_len))		
	peaks <- which(knnmax(x, index=index, k=k))
	ann <- data.frame(row.names=seq_along(peaks))
	if ( isTRUE(relheight) || is.numeric(relheight) )
	{
		ann$relheight <- x[peaks] / max(x[peaks])
		if ( is.numeric(relheight) )
		{
			# filter based on relative height if requested
			keep <- ann$relheight >= relheight
			peaks <- peaks[keep]
			ann <- ann[keep,,drop=FALSE]
		}
	}
	if ( isTRUE(snr) || is.numeric(snr) )
	{
		fn <- estnoise_fun(noise)
		noise <- fn(x, index=index, ...)
		ann$snr <- x[peaks] / noise[peaks]
		if ( is.numeric(snr) )
		{
			# filter based on SNR
			keep <- ann$snr >= snr
			peaks <- peaks[keep]
			ann <- ann[keep,,drop=FALSE]
		}
	}
	if ( isTRUE(arr.ind) )
	{
		if ( is.null(dim(x)) )
			matter_error("x must be an array when arr.ind=TRUE")
		peaks <- arrayInd(peaks, dim(x), dimnames(x), useNames=useNames)
	}
	if ( length(ann) > 0L )
		peaks <- update_attr(peaks, ann)
	peaks
}

#### Colocalization coefficients ####
## ----------------------------------

coscore <- function(x, y, threshold = NA)
{
	x <- replace(x, is.na(x), FALSE)
	y <- replace(y, is.na(y), FALSE)
	if ( any(x < 0) )
		x <- x + min(x)
	if ( any(y < 0) )
		y <- y + min(y)
	if ( is.function(threshold) )
		threshold <- c(threshold(x), threshold(y))
	if ( anyNA(threshold) && (!is.logical(x) || !is.logical(y)) )
	{
		fit <- lr(as.vector(x), as.vector(y))
		Tx <- seq.default(max(x), min(x), length.out=128L)
		Ty <- vapply(Tx, function(tt) lr_predict(fit, tt), numeric(1L))
		for ( i in seq_along(Tx) )
		{
			xt <- ifelse(x < Tx[i], x, 0)
			yt <- ifelse(y < Ty[i], y, 0)
			if ( sd(xt) <= 0 || sd(yt) <= 0 ) {
				threshold <- c(Tx[i], Ty[i])
				break
			}
			r <- cor(as.vector(xt), as.vector(yt))
			if ( r <= 0 ) {
				threshold <- c(Tx[i], Ty[i])
				break
			}
		}
	}
	threshold <- rep_len(threshold, 2L)
	if ( is.logical(x) ) {
		xm <- x
	} else {
		xm <- ifelse(x >= threshold[1L], x, 0)
	}
	if ( is.logical(y) ) {
		ym <- y
	} else {
		ym <- ifelse(y >= threshold[2L], y, 0)
	}
	ans <- c(
		MOC=sum(xm * ym) / sqrt(sum(xm^2) * sum(ym^2)),
		M1=sum(ifelse(ym > 0, xm, 0)) / sum(xm),
		M2=sum(ifelse(xm > 0, ym, 0)) / sum(ym),
		Dice=2 * sum(xm > 0 & ym > 0) / (sum(xm > 0) + sum(ym > 0)))
	structure(ans, threshold=threshold)
}

#### Rasterize a scattered image ####
## ----------------------------------

is_gridded <- function(x, tol = sqrt(.Machine$double.eps))
{
	if ( length(dim(x)) != 2L )
		matter_error("x must be a matrix or data frame")
	all(is.finite(apply(x, 2L, estres, tol=tol, ref="abs")))
}

to_raster <- function(x, y, vals)
{
	xy <- cbind(x, y)
	gridded <- is_gridded(xy)
	dm <- estdim(xy)
	nx <- dm[1L]
	ny <- dm[2L]
	xr <- range(x, na.rm=TRUE)
	yr <- range(y, na.rm=TRUE)
	rx <- (xr[2L] - xr[1L]) / (nx - 1)
	ry <- (yr[2L] - yr[1L]) / (ny - 1)
	rx <- ifelse(is.na(rx), 1, rx)
	ry <- ifelse(is.na(ry), 1, ry)
	if ( gridded || !is.numeric(vals) ) {
		rows <- as.integer(round((x - xr[1L]) / rx))
		cols <- as.integer(round((y - yr[1L]) / ry))
		init <- as.vector(NA, mode=typeof(vals))
		rs <- matrix(init, nrow=nx, ncol=ny)
		ind <- ((cols * nx) + rows) + 1L
		vals <- vals[!is.na(ind)]
		ind <- ind[!is.na(ind)]
		ind[ind > length(rs)] <- length(rs)
		rs[ind] <- vals
	} else {
		rs <- approx2(x, y, vals, interp="none",
			nx=nx, ny=ny, tol=c(rx, ry) / 2)
	}
	rs
}

to_raster3 <- function(x, y, z, vals)
{
	xyz <- cbind(x, y, z)
	gridded <- is_gridded(xyz)
	if ( !gridded )
		matter_warn("data is not gridded")
	dm <- estdim(xyz)
	xr <- range(x, na.rm=TRUE)
	yr <- range(y, na.rm=TRUE)
	zr <- range(z, na.rm=TRUE)
	nx <- dm[1L]
	ny <- dm[2L]
	nz <- dm[3L]
	rx <- (xr[2L] - xr[1L]) / (nx - 1)
	ry <- (yr[2L] - yr[1L]) / (ny - 1)
	rz <- (zr[2L] - zr[1L]) / (nz - 1)
	rx <- ifelse(is.na(rx), 1, rx)
	ry <- ifelse(is.na(ry), 1, ry)
	rz <- ifelse(is.na(rz), 1, rz)
	ix <- as.integer(round((x - xr[1]) / rx))
	iy <- as.integer(round((y - yr[1]) / ry))
	iz <- as.integer(round((z - zr[1]) / rz))
	init <- as.vector(NA, mode=typeof(vals))
	rs <- array(init, dim=c(nx, ny, nz))
	ind <- ((iz * nx * ny) + (iy * nx) + ix) + 1L
	ind[ind > length(rs)] <- length(rs)
	rs[ind] <- vals
	rs
}

estdim <- function(x, tol = 1e-6)
{
	if ( length(dim(x)) != 2L )
		matter_error("x must be a matrix or data frame")
	res <- apply(x, 2L, estres, tol=tol, ref="abs")
	if ( anyNA(res) ) {
		if ( ncol(x) == 2L ) {
			xr <- range(x[,1L], na.rm=TRUE)
			yr <- range(x[,2L], na.rm=TRUE)
			asp <- diff(yr) / diff(xr)
			nx <- round(sqrt(nrow(x) / asp))
			ny <- round(asp * nx)
			dm <- c(nx, ny)
		} else if ( ncol(x) == 3L ) {
			xr <- range(x[,1L], na.rm=TRUE)
			yr <- range(x[,2L], na.rm=TRUE)
			zr <- range(x[,3L], na.rm=TRUE)
			axy <- diff(yr) / diff(xr)
			axz <- diff(zr) / diff(xr)
			ayz <- diff(zr) / diff(yr)
			nx <- round((nrow(x) / (axz * axy))^(1/3))
			ny <- round((axz / ayz) * nx)
			nz <- round((ayz * axy) * nx)
			dm <- c(nx, ny, nz)
		} else {
			matter_error("only 2 or 3 dimensions supported",
				" for irregular coordinates")
		}
		names(dm) <- colnames(x)
	} else {
		dx <- apply(x, 2L, function(xi) diff(range(xi)))
		dm <- (dx / res) + 1
	}
	dm
}

#### 2D Resampling with interpolation ####
## ---------------------------------------

approx2 <- function(x, y, z, xout, yout,
	interp = "linear", nx = length(x), ny = length(y),
	tol = NA_real_, tol.ref = "abs", extrap = NA_real_)
{
	if ( is.matrix(x) && missing(y) && missing(z) ) {
		z <- x
		x <- seq_len(nrow(z))
		y <- seq_len(ncol(z))
	}
	if ( missing(xout) )
		xout <- seq(from=min(x), to=max(x), length.out=nx)
	if ( missing(yout) )
		yout <- seq(from=min(y), to=max(y), length.out=ny)
	lx <- length(x)
	ly <- length(y)
	if ( is.matrix(z) && all(c(lx, ly) == dim(z)) ) {
		co <- expand.grid(x=x, y=y)
		x <- co$x
		y <- co$y
	}
	nx <- length(xout)
	ny <- length(yout)
	out <- expand.grid(x=xout, y=yout)
	xi <- out$x
	yi <- out$y
	if ( length(x) != length(y) || length(y) != length(z) )
		matter_error("x, y, z must all be the same length")
	xy <- as.matrix(cbind(x, y))
	if ( is.integer(xy) && (is.double(xi) || is.double(yi)) )
		storage.mode(xy) <- "double"
	if ( is.double(xy) && is.integer(xi) )
		storage.mode(xi) <- "double"
	if ( is.double(xy) && is.integer(yi) )
		storage.mode(yi) <- "double"
	tol <- rep_len(tol, 2L)
	if ( anyNA(tol) ) {
		# guess tol as ~1x/~2x the estimated gap between samples
		ref <- ifelse(tol.ref == "abs", "abs", "y")
		mul <- ifelse(interp %in% c("none", "linear"), 1, 2)
		tolx <- mul * reldiff(range(x), ref=ref) / sqrt(length(x))
		toly <- mul * reldiff(range(y), ref=ref) / sqrt(length(y))
		newtol <- c(tolx, toly)
		tol[is.na(tol)] <- newtol[is.na(tol)]
	}
	extrap <- as.numeric(extrap)
	zi <- .Call(C_Approx2, xi, yi, xy, z, tol, as_tol_ref(tol.ref),
		extrap, as_interp(interp), PACKAGE="matter")
	dim(zi) <- c(nx, ny)
	zi
}

#### Affine transformation ####
## ----------------------------

trans2d <- function(x, y, z, pmat,
	rotate = 0, translate = c(0, 0), scale = c(1, 1),
	interp = "linear", dimout = dim(z), ...)
{
	if ( missing(pmat) ) {
		angle <- rotate * pi / 180
		sc <- rep_len(scale, 2L)
		r1 <- sc[1L] * c(cos(angle), -sin(angle))
		r2 <- sc[2L] * c(sin(angle), cos(angle))
		tl <- rep_len(translate, 2L)
		pmat <- rbind(r1, r2, tl)
	} else {
		pmat <- as.matrix(pmat)
	}
	if ( !identical(dim(pmat), c(3L, 2L)) )
		matter_error("pmat must be a 3 x 2 matrix")
	if ( is.array(x) && missing(y) && missing(z) ) {
		z <- x
		x <- seq_len(nrow(z))
		y <- seq_len(ncol(z))
		co <- expand.grid(x=x, y=y)
		x <- co$x
		y <- co$y
	}
	co <- cbind(x, y, 1) %*% pmat
	co <- data.frame(x=co[,1], y=co[,2])
	if ( !missing(z) ) {
		xi <- seq(from=min(x), to=max(x), length.out=dimout[1L])
		yi <- seq(from=min(y), to=max(y), length.out=dimout[2L])
		if ( length(dim(z)) > 2L ) {
			if ( length(dim(z)) > 3L )
				matter_error("arrays with more than 3 dimensions not allowed")
			zi <- lapply(seq_len(dim(z)[3L]), function(i, ...)
				{
					approx2(co$x, co$y, z=z[,,i], interp=interp,
						xout=xi, yout=yi, ...)
				}, ...)
			zi <- array(unlist(zi), dim=c(dim(zi[[1L]]), length(zi)))
		} else {
			zi <- approx2(co$x, co$y, z=z, interp=interp,
				xout=xi, yout=yi, ...)
		}
		structure(zi, coord=co)
	} else {
		co
	}
}
kuwisdelu/matter documentation built on Oct. 19, 2024, 10:31 a.m.