Skip to content

For developers

dorianps edited this page Sep 14, 2018 · 44 revisions

The LESYMAP package can benefit from the contribution of other developers. You can write your own functions, integrate them in LESYMAP, and push your changes in Github. If you have never written an R function before, you may need to form an idea of what is a function first. Maybe this page can help you start.

The following paragraphs explain how the LESYMAP package is organized, so you can integrate your code easily.

The LESYMAP structure

All LESYMAP functions can be classified in three main components.

  1. The lesymap.R file.
    This is the generic input-output function that takes your data, runs appropriate checks, transforms the images into a matrix of voxels, submits the matrix and behavioral scores to one of the lsm_* functions, and puts back the results in images. lesymap.R is the central interface of the software. The output of lesymap.R is a list object with all the details of the analyses and the results. Beside the attribute list the object has an additional attribute lesymap which marks it in R when print or plot functions are called (i.e., you can see a summary of the results simply by typing the name of the variable).
    Typically you will not need to edit or change lesymap.R itself, unless you fix a bug or make a major contribution to the package.

  2. The lsm_*.R files
    These are the statistical engines of LESYMAP. If you want to add a new type of analysis, you will probably need to add a new lsm_* function. A lsm_* function takes a matrix (often called lesmat in the code, which contains voxel values) and a vector (the behavioral scores). The function runs a specific analysis and returns a list object with two vectors:

  • statistic (i.e., the t-score) and
  • pvalue (statistical probability).
    The returned list of outputs is used by lesymap.R to create the final statistical brain maps (i.e., stat.img and pval.img). In other words, the values of statistic and pvalue vectors are put back into the corresponding voxels in the image. Beside the required vectors, the output of lsm_* can contain other objects; e.g zscore, FWEthreshold, etc. It can also contain a single character note, e.g. 'The function did not converge'. All these additional objects are added to the final LESYMAP output.
    If you want to add a new type of analysis in LESYMAP, you can create a new lsm_* function (e.g., lsm_SVR), which takes the voxel matrix and behavioral score and returns back a list with statistic and pvalue. If your new method does not produce p-values you can return a pvalue vector filled with 1, but you still have to return a pvalue vector as output. The current lsm_sccan function does this as well.
    N.b., the vectors returned by lsm_* functions (i.e., statistic, pvalue, etc.) must be of the same length as the number of columns in the voxel matrix, because each voxel must have a value to put back in the image.
    The lsm_* functions can call other functions, they don't need to be self-standing and perform all processes by themselves. As an example, the current lsm_regresfast functions calls internally regresfast, which is a fast C++ compiled analysis engine. Once regresfast has returned the statistical values, lsm_regresfast performs a few more checks and returns the results. In other words, you can design your contributions to be modular. If you are wondering why don't we call regresfast directly instead of calling lsm_regresfast that is because there are several checks and post-processing steps which are better off performed in R than in C++.
    Important, the lsm_* functions may get several arguments that are not available in the main lesymap function. However, the user can still set those arguments, which will go in the ... object and passed along to functions that are called. In this example:
    lesymap(lesions, behavior, method='regresfast', covariates=cbind(age,gender))
    the argument covariates is not a lesymap argument, but a lsm_regresfast argument. The user can still use it, and what happens is that the argument is passed in a chain lesymap -> lsm_regresfast -> regresfast.
  1. The helper functions.
    LESYMAP contains other functions that are not essential but helpful to support the processing pipeline. For example getUniqueLesionPatches computes unique voxels and prepares the data that can be used by lesymap.R and lsm_*. Similarly print.lesymap prints the results of a lesymap object in the console. The registerLesionToTemplate is standalone helper function which registers lesion maps in template space.

A practical example

Let's suppose you want to add a new function to perform lesion-to-symptom mapping with random forests. First, you can create a file named lsm_RFs.R. In it, you can define the function that will take the behavior vector and the voxel matrix, and will output statistic and pvalue. I.e., something like:

lsm_RFs <- function(lesmat, behavior) {

  # your analysis here


  output = list()
  output$statistic = yourstats
  output$pvalue = yourpvalues

  return(output)
}

The importance of documentation

Please consider that someone will need to read your code sooner your later (including yourself). For this reason, your variables should have meaningful names. For example, a variable name statistic is meaningful, while a variable name krstat2 is not.

Second, please add comments to the code, to explain what is being done or why. It is always helpful to read in plain English what the code is doing rather than figuring this out from the code. Something like this would be helpful:

# checking for potential infinite values,
# may happen in BM tests
if (any(is.inf(statistic))) 
  stop('Infite values found in statistics, something might be wrong`)

Third, each added function requires a documentation header. This documentation is what you see in R when you type i.e. ?lesymap or ?lsm_sccan. A specific package is used (roxygen) to build that documentation from the documentation header. Your final function with included documentation may look something like this:

#' lsm_RFs
#'
#' Lesion to symptom mapping performed on a prepared matrix.
#' Random forests are used to analyze the data.
#'
#' @param lesmat binary matrix (0/1) of voxels (columns)
#' and subjects (rows).
#' @param behavior vector of behavioral scores.
#' @param ... other arguments received from \code{\link{lesymap}}.
#'
#' @return
#' List of objects returned:
#' \itemize{
#'  \item\code{statistic} - vector of statistical values
#'  \item\code{pvalue} - vector of pvalues
#' }
#'
#' @examples{
#' set.seed(123)
#' lesmat = matrix(rbinom(200,1,0.5), ncol=2)
#' set.seed(123)
#' behavior = rnorm(100)
#' result = lsm_RFs(lesmat, behavior)
#' }
#'
#' @author FirstName LastName
#'
#' @export
lsm_RFs <- function(lesmat, behavior, ...) {

  # your analysis here


  output = list()
  output$statistic = yourstats
  output$pvalue = yourpvalues

  return(output)
}

Fourth, you can use RStudio and open the LESYMAP.proj project. This will open the environment for you to quickly install the package with all your changes, so you can check how the software will work after your changes. When you are done editing, you should can press "Check" in the "Build" tab on the right (or use devtools::check()), and a thorough check will be performed in search of potential errors. This check is also performed automatically on Github if you push the changes online.

Using Github

LESYMAP is hosted in Github, a platform that facilitates enormously the tracking of software changes and crowd-sourcing. In Github you can go back at any development stage and check the history of how each file was changed in the past. To integrate your code in LESYMAP you need to make the changes in a branch of your own, then create a pull request. Your changes will be automatically checked for coding or compiling errors in the Travis platform, and then will be evaluated by a human. If the changes are useful to the community and do not compromise the existing LESYMAP functionality, your code will be merged with the master branch.
If you don't know how to create a pull request in Github, or what is a software branch, please google those terms and learn the basics. The learning curve is typically quick. If you don't like the learning process... well, you you probably don't like programming either. :)

That's it, feel free to open a github issue if you embark in the endeavor of helping out with LESYMAP development.