#' Simple plotBasicNetwork function.
#'
#' takes an object output from renderSurfaceFunction and a list of centroids
#' and plots the centroid network over the rendering object
#'
#' If \code{edgecolors} is not specified, a heat-like color palette is used. Weights
#' can be quantile transformed or clipped at a given quantile in order to improve
#' contrast.
#'
#' If weights are not specified, only the nodes are plotted.
#'
#' @param centroids input matrix of size N 3D points ( in rows ) by 3 (
#' in columns ), for N nodes.
#' @param brain input rendering object which is output of renderSurfaceFunction.
#' or a function derived from renderSurfaceFunction.
#' @param weights edge weights, a symmetric matrix of size N. Weights should be non-negative.
#' @param nodecolors a color or color vector for nodes.
#' @param edgecolors a color(map) for edges. If a color map function, weights will be transformed
#' to the range [0,1], which is compatible with functions returned by \code{colorRamp}.
#' @param backgroundColor background color.
#' @param nodetype sphere or other node type supported by RGL.
#' @param edgeContrast a vector of length 2, specifying the contrast range for edge colors.
#' Weights are normalized to the range [0,1].
#' The normalized weights can be rescaled with this parameter, eg \code{c(0.05,0.95)}
#' would stretch the contrast over the middle 90\% of normalized weight values.
#' @param quantileTransformWeights quantile transform the weights.
#' @param lwd line width for drawing edges.
#' @param minRadius minimum node radius. Ignored if the radius is specified explicitly with \code{radius}.
#' @param maxRadius maximum node radius. The node radius between \code{minRadius} and \code{maxRadius} is
#' determined from the sum of the edge weights connecting the node. Ignored if the radius is specified
#' explicitly with \code{radius}.
#' @param radius a constant radius or vector of length nrow(centroids). If not specified, node radius is
#' determined by the sum of edge weights connected to the node.
#' @param showOnlyConnectedNodes boolean, if \code{TRUE}, only nodes with non-zero edge weights are plotted.
#'
#' @return None
#' @author Avants BB, Duda JT, Cook PA
#' @examples
#' \dontrun{
#' # more complete example
#' mnit <- getANTsRData("mni")
#' mnit <- antsImageRead(mnit)
#' mnia <- getANTsRData("mnia")
#' mnia <- antsImageRead(mnia)
#' mnit <- thresholdImage(mnit, 1, max(mnit))
#' mnit <- iMath(mnit, "FillHoles")
#' mnit <- thresholdImage(mnit, 1, 2)
#' cnt <- getCentroids(mnia, clustparam = 0)
#' aalcnt <- cnt[1:90, 1:3]
#' brain <- renderSurfaceFunction(
#' surfimg = list(mnit),
#' alphasurf = 0.1, smoothsval = 1.5
#' )
#' testweights <- matrix(rep(0, 90 * 90), nrow = 90)
#' testweights[31, 37] <- 1 # ant cingulate to hipp
#' testweights[31, 36] <- 2 # ant cingulate to post cingulate
#' testweights[11, 65] <- 3 # broca to angular
#' plotBasicNetwork(centroids = aalcnt, brain, weights = testweights, edgecolors = "red")
#' id <- rgl::par3d("userMatrix")
#' rid <- rotate3d(id, -pi / 2, 1, 0, 0)
#' rid2 <- rotate3d(id, pi / 2, 0, 0, 1)
#' rid3 <- rotate3d(id, -pi / 2, 0, 0, 1)
#' rgl::par3d(userMatrix = id)
#' dd <- make3ViewPNG(rid, id, rid2, paste("network1", sep = ""))
#' rgl::par3d(userMatrix = id)
#' # another example
#' mni <- getANTsRData("mni")
#' mni <- antsImageRead(mni)
#' mnit <- thresholdImage(mni, 1, max(mni))
#' mnit <- iMath(mnit, "FillHoles")
#' mniseg <- kmeansSegmentation(mni, 3)$segmentation
#' wmbkgd <- thresholdImage(mniseg, 3, 3) %>%
#' iMath("GetLargestComponent") %>%
#' iMath("FillHoles")
#' wmbkgd <- smoothImage(iMath(wmbkgd, "MD", 1), 2.0)
#' brain <- renderSurfaceFunction(
#' surfimg = list(wmbkgd),
#' alphasurf = 0.8, smoothsval = 1.0
#' )
#' data(powers_areal_mni_itk)
#' coords <- powers_areal_mni_itk[, 1:3]
#' id <- rgl::par3d("userMatrix")
#' rid <- rotate3d(id, -pi / 2, 1, 0, 0)
#' rid2 <- rotate3d(id, pi / 2, 0, 0, 1)
#' rid3 <- rotate3d(id, -pi / 2, 0, 0, 1)
#' rgl::par3d(userMatrix = id)
#' handMat2 <- t(matrix(c(
#' -0.9998656511, 0.01626961, 0.00198165,
#' 0, -0.0163816363, -0.99584705, -0.08955579, 0, 0.0005163439,
#' -0.08957647, 0.99597979, 0, 0.0000000000, 0.00000000,
#' 0.00000000, 1
#' ), ncol = 4))
#' loccolor <- as.character(powers_areal_mni_itk$Color)
#' loccolor[loccolor == "Peach"] <- "sienna1"
#' loccolor[loccolor == "Cyan"] <- "cyan"
#' loccolor[loccolor == "Orange"] <- "orange"
#' loccolor[loccolor == "Purple"] <- "darkorchid1"
#' loccolor[loccolor == "Pink"] <- "deeppink"
#' loccolor[loccolor == "Red"] <- "red"
#' loccolor[loccolor == "Gray"] <- "gray74"
#' loccolor[loccolor == "Teal"] <- "turquoise4"
#' loccolor[loccolor == "Blue"] <- "blue"
#' loccolor[loccolor == "Yellow"] <- "yellow"
#' loccolor[loccolor == "Black"] <- "black"
#' loccolor[loccolor == "Brown"] <- "brown"
#' loccolor[loccolor == "Pale blue"] <- "steelblue1"
#' loccolor[loccolor == "Green"] <- "green"
#' tt <- plotBasicNetwork(
#' centroids = coords, brain,
#' nodecolors = loccolor, radius = 3
#' )
#' dd <- make3ViewPNG(handMat2, id, rid2, tempfile(fileext = ".png"))
#' rgl::par3d(userMatrix = id)
#' }
#'
#' @export plotBasicNetwork
plotBasicNetwork <- function(centroids,
brain,
weights = NA,
edgecolors = -1,
backgroundColor = "white",
nodecolors = "blue",
nodetype = "s",
edgeContrast = c(0, 1),
quantileTransformWeights = FALSE,
lwd = 2,
minRadius = 0,
maxRadius = 3,
radius = NA,
showOnlyConnectedNodes = TRUE) {
if (missing(centroids) | missing(brain)) {
print(args(plotBasicNetwork))
return(1)
}
if (!usePkg("rgl")) {
print("rgl is necessary for this function.")
return(NULL)
}
edgeColorsIsFunction <- is.function(edgecolors)
# Test if weights are all NA here. Then use this boolean in place of (is.na(weights), which
# causes warnings in scalar contexts like if statements
weightsNA <- all(is.na(weights))
numNodes <- nrow(centroids)
rgl::rgl.bg(color = backgroundColor)
rgl::par3d(windowRect = c(100, 100, 600, 600))
mesh <- .getvertices(brain[[1]])
nSurfaceVerts <- dim(mesh$vertices)[1]
mesh$vertices <- rbind(mesh$vertices, as.matrix(centroids))
labelVerts <- c(1:nrow(centroids)) + nSurfaceVerts
if (weightsNA) {
if (all(is.na(radius))) radius <- 3
rgl::spheres3d(mesh$vertices[labelVerts, ], color = nodecolors, type = nodetype, radius = radius)
return(1)
}
if (!(length(dim(weights) == 2 && dim(weights)[1] == numNodes && dim(weights)[2] == numNodes))) {
stop("Weights must be a 2D symmetric matrix with one entry for each pair of nodes")
}
if (all(is.na(radius))) { # node scaled by strength
radiusw <- rep(0, nrow(centroids))
radiusscale <- as.numeric(apply(weights, FUN = sum, MARGIN = 1, na.rm = T) +
apply(weights, FUN = sum, MARGIN = 2, na.rm = T))
radiusscale <- (radiusscale / max(radiusscale))
nodeMask <- 1 * (radiusscale > 0) # unconnected nodes stay at 0 if showOnlyConnectedNodes
radiusw <- (minRadius + (maxRadius - minRadius) * radiusscale)
if (showOnlyConnectedNodes) {
radiusq <- radiusw * nodeMask
}
radius <- radiusw
}
ggg <- weights
luniqvals <- length(unique(ggg[ggg > 0]))
binaryWeights <- FALSE
if (luniqvals == 1) {
binaryWeights <- TRUE
# If some constant that is not binary, convert to binary so that we don't mess up
# color mapping
weights <- weights / max(weights)
} else if (quantileTransformWeights) {
if (luniqvals > 250) ncuts <- 1.0 / 250.0 else ncuts <- 1.0 / (luniqvals * 0.5)
qqq <- quantile(ggg[ggg > 0], probs = seq(0, 1, by = ncuts), na.rm = TRUE, names = FALSE, type = 7)
qqq <- unique(qqq)
myquartile <- cut(ggg, breaks = qqq, include.lowest = TRUE)
myquartile <- as.numeric(myquartile)
myquartile[is.na(myquartile)] <- 0
myquartile[is.nan(myquartile)] <- 0
weights <- matrix(myquartile, nrow = nrow(weights))
}
rgl::spheres3d(mesh$vertices[labelVerts, ], color = nodecolors, type = nodetype, radius = radius)
edgelocations <- c()
edgeweights <- c()
for (i in c(1:nrow(weights))) {
for (j in c(1:ncol(weights))) {
if (weights[i, j] > 0 & weights[i, j] < Inf) {
# Draw each edge once only
if (i < j) {
edgelocations <- c(edgelocations, nSurfaceVerts + c(i, j))
edgeweights <- c(edgeweights, weights[i, j])
}
}
}
}
# normalize weights to range [0-1]
edgeweightsNorm <- edgeweights
if (!binaryWeights) {
minWeight <- min(edgeweights)
maxWeight <- max(edgeweights)
edgeweightsNorm <- (edgeweights - minWeight) / (maxWeight - minWeight)
# user defined contrast stretch
# Want to map (0.05, 0.95) to (0,1)
# then clip values outside this range
edgeweightsNorm <- (edgeweightsNorm - edgeContrast[1]) / (edgeContrast[2] - edgeContrast[1])
edgeweightsNorm[edgeweightsNorm > 1] <- 1
edgeweightsNorm[edgeweightsNorm < 0] <- 0
}
# Map colors unless color map or other explicit color scheme provided.
if (!edgeColorsIsFunction && (length(edgecolors) == 1) && (edgecolors[1] == -1)) {
# heat.colors makes high end white, which is invisible with default background
colormap <- colorRampPalette(c("#650000", "dark red", "red", "darkorange", "orange", "yellow", "#FAFAD0"))
edgecolors <- edgeweightsNorm
colorArr <- colormap(256)
for (i in c(1:length(edgeweightsNorm))) {
edgecolors[i] <- colorArr[1 + floor(edgeweightsNorm[i] * 255)]
}
}
if (edgeColorsIsFunction) {
# Convert function to color array
colSeq <- seq(0, 1, 1 / 255)
colorArr <- rgb(edgecolors(colSeq), maxColorValue = 255)
edgecolors <- edgeweightsNorm
for (i in c(1:length(edgeweightsNorm))) {
edgecolors[i] <- colorArr[1 + floor(edgeweightsNorm[i] * 255)]
}
}
rgl::segments3d(mesh$vertices[edgelocations, ], col = edgecolors, lwd = lwd)
}
.getvertices <- function(inrglmesh) {
cter <- nrow(inrglmesh[[1]])
vertices <- matrix(NA, nrow = 3 * cter, ncol = 3)
inds <- c(1:cter)
vertices[(3 * inds - 2), ] <- inrglmesh[[1]]
vertices[(3 * inds - 1), ] <- inrglmesh[[2]]
vertices[(3 * inds - 0), ] <- inrglmesh[[3]]
indices <- rep(NA, nrow(vertices))
return(list(vertices = vertices, indices = indices))
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.