---
title: "The fsbrain FAQ"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{The fsbrain FAQ}
  %\VignetteEngine{knitr::rmarkdown}
  \usepackage[utf8]{inputenc}
---

## Input data

### **Q**: What kind of input data do I need for fsbrain?

The *fsbrain* software is designed to be used with the output of [FreeSurfer](http://freesurfer.net/).  Running *recon-all* on your T1w MRI scan results in a directory structure full of different files and file types for each subject. The *fsbrain* library uses knowledge on this directory layout to load the proper data.

However, while designed primarily with FreeSurfer in mind, fsbrain is not limited to FreeSurfer output, see below.

### **Q**: Which file formats are supported? 

The *fsbrain* library uses [freesurferformats](https://github.com/dfsp-spirit/freesurferformats) to load a variety of neuroimaging file formats, including data exchange formats used by other brain imaging software. See the [freesurferformats website](https://github.com/dfsp-spirit/freesurferformats) for the full list.

### **Q**: I want to load a single file that is *somewhere* on my harddisk, i.e., not within a standard recon-all output directory structure. How can I load it?

You can use [freesurferformats](https://github.com/dfsp-spirit/freesurferformats) directly to load the data, then pass it to *fsbrain*. See the next question for an example.

### **Q**: Can I use fsbrain to visualize data from SPM12 / CAT12 or other software packages?

Yes, the [computational anatomy toolbox (CAT12)](http://www.neuro.uni-jena.de/cat/) for [SPM](https://www.fil.ion.ucl.ac.uk/spm/) writes surfaces in GIFTI format and the morphometry data in curv format, both formats are supported by *fsbrain*. After running CAT12 surface measure computation on your subject `subject1`, you should have the following files in the *surf/* subdir:

* lh.central.subject1.gii
* lh.gyrification.subject1

Try the following to visualize the gyrification data for the left hemisphere in *fsbrain*:

```{r, eval = FALSE}
lh_surf = freesurferformats::read_nisurface('~/data/subject1_spm12/surf/lh.central.subject1.gii');
lh_gyrification = freesurferformats::read.fs.curv('~/data/subject1_spm12/surf/lh.gyrification.subject1');
vis.data.on.subject('~/data/', 'subject1_spm12', lh_gyrification, NULL, surface=lh_surf);
```


You should be able to load data from a number of different neuroimaging software packages with freesurferformats, as it supports the very common NIFTI and GIFTI file formats.


## Visualization


### How can I set the output image resolution?

To increase the output resolution, you need to increase the size of the *rgl* rendering device. To do this globally, before you call any *fsbrain* rendering function:

```{r, eval = FALSE}
library('rgl');
r3dDefaults$windowRect=c(20, 20, 1800, 1200);
```

This instructs *rgl* to create new rendering devices at screen position 20, 20 (close to the upper left corner) with a size of 1800x1200 pixels.

Alternatively, you can control the size when calling an *fsbrain* visualization function by passing the same information in the optional `rgloptions` parameter, like this:

```{r, eval = FALSE}
rglo = list('windowRect'=c(20, 20, 1800, 1200));
vis.subject.morph.native('~/mysubjects_dir', 'subject1', 'thickness', rgloptions=rglo);
```

###  How can I save high quality figures to PNG images?

Note that fsbrain renders images, which means the output is pixel-based (i.e., bitmap as opposed to vector graphics). To get high quality output, you need to increase the size of the *rgl* rendering device, as explained in the last question.

To save the plot to a file in PNG format, you can use an rglaction:

```{r, eval = FALSE}
rgla = list('snapshot_png'='~/subject1_thickness.png');
vis.subject.morph.native('~/mysubjects_dir', 'subject1', 'thickness', rglactions=rgla);
```

This opens the plot in a window as usual and also saves it in PNG format to the file *subject1_thickness.png* in your home directory.


### How can I change the colormap for the plots?

Pass a colormap function to any visualization function that supports the *makecmap_options* parameter, as entry *colFn* like illustrated below:

```{r, eval = FALSE}
makecmap_options = list('colFn'=viridis::viridis);
vis.subject.morph.native('~/mysubjects_dir', 'subject1', 'thickness', makecmap_options=makecmap_options);
```

In that example, we used the popular *viridis* colormap. In R, it is available from the *viridis* package. If you don't have it, you can install it with:

```{r, eval = FALSE}
install.packages('viridis');
```


Of course, you can use any colormap function you want, currently the only limitation is that it should accept an integer parameter: the requested number of colors. 

The exact number of colors that will be requested depends on your data, and if the colormap you want only supports very few colors, you can use a wrapper function to interpolate. Here is an example for the very popular *RColorBrewer* package. Some of its colormaps have less than 10 colors, which is usually not enough for neuroimaging data. Here we wrap the 'Blues' palette, which has 9 colors:

```{r, eval = FALSE}
colFn_many_blues = colorRampPalette(RColorBrewer::brewer.pal(9, name="Blues"));
makecmap_options = list('colFn'=colFn_many_blues);
vis.subject.morph.native('~/mysubjects_dir', 'subject1', 'thickness', makecmap_options=makecmap_options);
```


### What are the best colormaps for my data?

Chosing a colormap can be a surprisingly complex question and there are many publications which discuss this topic. You may want to consider what kind of data you have and what property you want to highlight, how many colors you need, whether you want colorblind-friendly colors, how they look when printed in grayscale, whether they look pleasing to you, and maybe many more dimensions.

The most important thing is to decide is whether you need a sequential, qualitative, or diverging palette for your data.

I am definitely not an expert, but here are some color functions I personally like and use with fsbrain:

```{r, eval = FALSE}
colFn_sequential = viridis::viridis;
colFn_qualitative = function(n) { RColorBrewer::brewer.pal(n, name="Set2"); } # n <= 11
colFn_diverging = grDevices::colorRampPalette(RColorBrewer::brewer.pal(11, name="RdBu"));
```

They require the *viridis* and *RColorBrewer* packages to be installed. The qualitative map is fine if you do not have more than 11 different values.

If you have R >= 3.6, you may not need any extra packages: have a look at the `grDevices::hcl.colors` function. Here are some suggestions:

```{r, eval = FALSE}
colFn_sequential = function(n) { grDevices::hcl.colors(n, palette = "viridis"); }
colFn_qualitative = function(n) { grDevices::hcl.colors(n, palette = "Dark 3"); }
colFn_diverging = function(n) { grDevices::hcl.colors(n, palette = "Blue-Red 3"); }
```


If you want a heatmap-style colormap (single hue sequential, yellow/red), try:

```{r, eval = FALSE}
colFn_sequential_heat = function(n) { grDevices::hcl.colors(n, "YlOrRd"); }
```

Make sure to read the next entry as well if you are using a diverging colormap.

### I am using a diverging colormap. When plotting the colorbar (legend), the neutral value is not at zero. How to fix it? 

When using a diverging colormap, make sure to set the *symm* option to *makecmap_options* when using a visualization function, like this:

```{r, eval = FALSE}
makecmap_options = list('colFn'=colFn_diverging, 'symm'=TRUE);
```

This ensures that the neutral color of the diverging colormap (usually white) is aligned with the zero mark in the colorbar/legend, by adapting the value range displayed on the colorbar.

### The colorbar shows only very few different colors, why is that and how can I change it?

The impression that the numbers of colors in the colorbar is lower than in the rendered image is a consequence of the rendering process: the lighting (shadows, highlights) and the material properties (glossyness, partial transparency) have an effect on the appearance of colors in the rendered image.

You can set the parameter *n* in the *makecmap_options* (see above) to request more colors, which will lead to a smooth colorbar.

```{r, eval = FALSE}
mkc = list('n'=200L);
vis.subject.morph.native('~/mysubjects_dir', 'subject1', 'thickness', makecmap_options=mkc);
```

This also means that more colors are used in the rendered image, but the effect will be less noticable.

### Can I get a color legend when plotting atlas regions from an annotation? I want to know the region names for the colors.

Yes, see the answer to the next question for details.

### Can I get a color legend when plotting a segmentation/parcellation (different brain regions), based on a color lookup table (like the *FreeSurferColorLUT.txt* file)?

Yes, use the `vis.colortable.legend` function. You can pass an annotation or a color lookup table, and it will create a plot that shows the colors and the structure (or region) names. The output will be a separate plot, so you can use standard R methods to save it in vector formats like PDF for best quality. 

Hint: you can load a color lookup table with `freesurferformats::read.fs.colortable`.


### There is too much whitespace between the different views of the brain. How can I reduce it?

While this is not possible in `rgl`, fsbrain provides the `vislayout.from.coloredmeshes` function to achieve this using Image Magick. You need to have the suggested 'magick' package installed for this to work. The function renders separate images, crops the output figures to remove the background, then merges the seperate cropped images into a final output image and saves it as a PNG file. Here is a usage example:

```{r, eval = FALSE}
# To get coloredmeshes return value only, ignore the visualization:
cm = vis.subject.morph.native(sjd, sj, 'thickness', makecmap_options = list('n'=200), cortex_only = T);
# Produce high quality tight layout:
vislayout.from.coloredmeshes(cm);
```

Note that your output resolution settings (see question above) now count for each of the single images. This means that you will get quite high resolution output in combination with the tight layout. This makes the function ideal for producing plots for publications.

You can adjust various settings, e.g., change the rendering stlyle, select different views, and save it to a custom file name in your home directory:

```{r, eval = FALSE}
output_brain_img = "fsbrain_arranged.png";
vislayout.from.coloredmeshes(cm, view_angles = get.view.angle.names(angle_set='t9'), output_img = output_brain_img);
```

It is also possible to plot a separate colorbar image and combine that with the tight layout brainview image. Note that the settings for the colorbar are stored in the coloredmeshes, and can be adjusted by altering the initial call to `vis.subject.morph.native` (or whatever visualization function you use) above.

```{r, eval = FALSE}
output_cbar_img = "fsbrain_cbar.png";
output_final_img = "fsbrain_merged.png";
coloredmesh.plot.colorbar.separate(cm, image.plot_extra_options = list('horizontal' = TRUE), png_options = list('filename'=output_cbar_img, 'width'=1800));
combine.colorbar.with.brainview.image(output_brain_img, output_cbar_img, output_final_img);
```

You may have to play a bit with the resolution settings of your brain images and the colorbar to get this right (the background cropping makes it hard to compute the exact values in advance). 

### When visualizing data on the inflated surface, the left and right hemisphere overlap and look garbled. How to fix that?

This happens due to the inflation. One can use the `rglactions` parameter to push the hemis apart. By default, it pushes them apart by the amount they overlap:

```{r, eval = FALSE}
vis.subject.morph.native(sjd, sj, 'thickness', rglactions = list('shift_hemis_apart'=TRUE), surface='inflated', views='si');
```

You can also set a distance manually:

```{r, eval = FALSE}
vis.subject.morph.native(sjd, sj, 'thickness', rglactions = list('shift_hemis_apart'=list('min_dist'=20)), surface='inflated', views='si');
```

If you need more customization options, have a look at the `shift.hemis.apart` function. If you pass a named list in the rglactions for 'shift_hemis_apart' (like in the previous example), the entries are passed on to that function.

## Various topics

### Can I integrate the plots produced by *fsbrain* into an R Markdown document (the R equivalent of a Python Jupyter notebook)?

Yes, see the example notebook files in the directory [web](https://github.com/dfsp-spirit/fsbrain/tree/master/web/Rmd_web_examples) of the *fsbrain* repository. The Rmd files are actually notebooks in R markdown format.

### Can I use fsbrain to visualize arbitrary triangular meshes in R?

Yes, *fsbrain* is not limited to brain surface meshes, and a wide array of mesh file formats are supported. Keep in mind though that fsbrain works with triangular meshes. To visualize a mesh from a file, the easiest way is to use `vis.fs.surface`:

```{r, eval = FALSE}
vis.fs.surface('~/Documents/my_mesh.ply');
```


## Technical problems

### I am getting the error 'invalid value specified for graphical parameter plt', why?

The full error most likely looks like this, or similar:

```{r, eval = FALSE}
Error in par(new = TRUE, pty = "m", plt = smallplot, err = -1) : 
  invalid value specified for graphical parameter "plt"
```


If you experience this error, it most likely happened when you tried to plot something with a colorbar, and did not increase the device size (image resolution). The error occurs if there is no space for the colorbar plot, and the solution is to increase the resolution as explained in the answer to the question 'How can I set the output image resolution?' above.


### I am getting the error 'Error in squash::cmap, found x values outside map range'

This can happen when visualizing morphometry data with function like `vis.subject.morph.native`. The full error most likely looks like this, or similar:

```{r, eval = FALSE}
vis.subject.morph.native(sjd, sj, 'your_measure_here')
Error in squash::cmap(lh_morph_data, map = common_cmap) : 
  Found 2193 values outside map range.
```

Most likely you have `Inf` values in your data, most likely in the medial wall, just try plotting without it:

```{r, eval = FALSE}
vis.subject.morph.native(sjd, sj, 'your_measure_here', cortex_only = T)
```

If this does not help, load the data and inspect it manually.


### I am getting the warning 'RGL: unable to open X11 display' when loading fsbrain

The full message most likely looks like this:

```{r, eval = FALSE}
Warning messages:
1: In rgl.init(initValue, onlyNULL) : RGL: unable to open X11 display
2: 'rgl.init' failed, running with 'rgl.useNULL = TRUE'.
```

This happens if you do not have X11, or no window can be opened. Possible reasons include that you are running R on a remote host using an SSH connection without X11 forwarding, or that you do not have XQuartz installed under MacOS.


