R/plotBasicNetwork.R

Defines functions .getvertices plotBasicNetwork

Documented in plotBasicNetwork

#' 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))
}
neuroconductor-devel/ANTsR documentation built on April 1, 2021, 1:02 p.m.