- Home
- Neuroconductor Tutorials
- Nifti basics
NIfTI Basics
John Muschelli
2021-02-16
All code for this document is located at here.
The nifti object
Note: Throughout this post, I will refer to an image on hard disk as a NIfTI, which is a file that generally has the extension “.nii” or “.nii.gz”. I will refer to the object in R as a nifti
(note the change of font and case).
In this tutorial we will discuss the basics of the nifti
object in R. There are many objects in R that represent imaging data. The Neuroconductor project chose the nifti
object from the oro.nifti
package as one of the the basic building blocks because it has been widely used in other packages, has been tested over a period of time, and inherits the properties of an array
in R.
To run this code, you must have oro.nifti
installed. You can either use the stable version on CRAN (using install.packages
) or the development version (using devtools::install_github
):
packages = installed.packages()
packages = packages[, "Package"]
if (!"oro.nifti" %in% packages) {
source("https://neuroconductor.org/neurocLite.R")
neuroc_install("oro.nifti")
### development version
# neuroc_install("oro.nifti", release = "stable")
}
S4 Implementation
As nifti
objects inherits the properties of an array
, you can perform a series of operations on them, such as addition/subtraction/division, as you would an array
. A nifti
object has additional attributes and the nifti
object is an S4 object. This means that you do not reference additional information using the $
operator.
library(oro.nifti)
set.seed(20161007)
dims = rep(10, 3)
arr = array(rnorm(10*10*10), dim = dims)
nim = oro.nifti::nifti(arr)
print(nim)
NIfTI-1 format
Type : nifti
Data Type : 2 (UINT8)
Bits per Pixel : 8
Slice Code : 0 (Unknown)
Intent Code : 0 (None)
Qform Code : 0 (Unknown)
Sform Code : 0 (Unknown)
Dimension : 10 x 10 x 10
Pixel Dimension : 1 x 1 x 1
Voxel Units : Unknown
Time Units : Unknown
print(class(nim))
[1] "nifti"
attr(,"package")
[1] "oro.nifti"
oro.nifti::is.nifti(nim)
[1] TRUE
Accessing Information from a nifti
To access additional information, called a slot, you can use the @
operator. We do not recommend this, as there should be a function implemented to “access” this slot. These are hence called accessor functions (they access things!). For example, if you want to get the cal_max
slot of a nifti
object, you should use the cal_max
function. If an accessor function is not implemented, you should still use the slot(object, name)
syntax over @
.
Here’s an example where we make an array of random normal data, and put that array into a nifti
object with the nifti
function:
nim@cal_max
[1] 2.706524
cal_max(nim)
[1] 2.706524
slot(nim, "cal_max")
[1] 2.706524
Accessing the “data”
If you want to access the “data” of the image, you can access that using:
data = slot(nim, ".Data")
class(data)
[1] "array"
With newer versions of oro.nifti
(especially that on GitHub and in Neuroconductor), there is a img_data
function to access the data:
data = oro.nifti::img_data(nim)
class(data)
[1] "array"
dim(data)
[1] 10 10 10
This array
is 3-dimensional and can be subset using normal square-bracket notations ([row, column, slice]
). Thus, if we want the 3rd “slice” of the image, we can use:
slice = data[,,3]
class(slice)
[1] "matrix" "array"
Thus we see we get a matrix of values from the 3rd “slice”. We should note that we generally reference an image by x, y, and z planes (in that order). Most of the time, the x direction refers to going left/right on an image, y refers to front/back (or anterior/posterior), and the z direction refers to up/down (superior/inferior). The actual direction depends on the header information of the NIfTI image.
slice = data[,,3, drop = FALSE]
class(slice)
[1] "array"
Show all slots
You can see which slots exist for a nifti
object by using slotNames
slotNames(nim)
[1] ".Data" "sizeof_hdr" "data_type" "db_name"
[5] "extents" "session_error" "regular" "dim_info"
[9] "dim_" "intent_p1" "intent_p2" "intent_p3"
[13] "intent_code" "datatype" "bitpix" "slice_start"
[17] "pixdim" "vox_offset" "scl_slope" "scl_inter"
[21] "slice_end" "slice_code" "xyzt_units" "cal_max"
[25] "cal_min" "slice_duration" "toffset" "glmax"
[29] "glmin" "descrip" "aux_file" "qform_code"
[33] "sform_code" "quatern_b" "quatern_c" "quatern_d"
[37] "qoffset_x" "qoffset_y" "qoffset_z" "srow_x"
[41] "srow_y" "srow_z" "intent_name" "magic"
[45] "extender" "reoriented"
If you would like to see information about each one of these slots, please see this blog post about the NIfTI header.
Other objects
Other packages, such as ANTsR
and RNifti
have implemented faster reading/writing functions of NIfTI images. These rely on pointers to object in memory and are very useful. They have specific implementations for extracting information from them and saving them out, such as in an Rda/rda (R data file). A series of conversion tools for ANTsR
objects are included in the extrantsr
package (function ants2oro
) and nii2oro
in oro.nifti
for RNifti objects.
NIfTI Input/Output: readnii
/writenii
vs. readNIfTI
/writeNIfTI
In the neurobase
package, we provide wrapper functions readnii
/writenii
, which wrap the oro.nifti
functions readNIfTI
/writeNIfTI
. There are a few reasons for this:
- You can pass a filename with a “.nii.gz” extension to
writenii
and an additional “.nii.gz” will not be added, whereas this will happen inwriteNIfTI
. writenii
will try to discern the data type of the image before writing, which may be useful if you created anifti
by copying information from a previousnifti
object.- The default in
readnii
isreorient = FALSE
, which generally does not error when reading in data, whereasreadNIfTI
defaults toreorient = TRUE
. This is discussed below. - Extraneous dimensions are automatically deleted with
readnii
. Note this may cause errors and is not desired 100% of the time.
Option reorient = FALSE
In readNIfTI
default reorient = TRUE
implicity uses the reorient
function from oro.nifti
. Although many neuroimaging software suites read the header and reorient the data based on that information, oro.nifti::reorient
can only handle simple orientations, see oro.nifti::performPermutation
documentation. Although reading the data in without reorienting can cause problems, such as not knowing right/left orientation, if multiple NIfTI files were created in the same way (assumingly from dcm2nii
), they should ideally have the same orientation.
Derived data from an image will have the exact same orientation because derived nifti
objects will copy the nifti
header information from the nifti
object it was derived from. Moreover, in many analyses, registration to an image or a template is common, and these have known orientations. We have found that if a user wants to reorient their data in R, using the reorient
function can be used, but we prefer the default to be FALSE
, otherwise reading in many NIfTI files result in an error from the orientation.
Operations of nifti
objects
Although the nifti
object is not a standard R object, you can perform standard operations on these objects, such as addition/subtraction and logic. This is referred to “overloaded” operators.
Logical operators
For example, if we want to create a nifti
object with binary values, where the values are TRUE
if the values in nim
are greater than 0, we can simply write:
above_zero = nim > 0
class(above_zero)
[1] "nifti"
attr(,"package")
[1] "oro.nifti"
img_data(above_zero)[1]
[1] TRUE
We will refer to binary images/nifti
objects as “masks”.
We can combine multiple operators, such as creating a binary mask for value greater than 0 and less than 2.
class(nim > 0 & nim < 2)
[1] "nifti"
attr(,"package")
[1] "oro.nifti"
Arithmetic on nifti
objects
We can also show the
class(nim * 2)
[1] "nifti"
attr(,"package")
[1] "oro.nifti"
class(nim + (nim / 4))
[1] "nifti"
attr(,"package")
[1] "oro.nifti"
class(nim * nim)
[1] "nifti"
attr(,"package")
[1] "oro.nifti"
class(nim^2)
[1] "nifti"
attr(,"package")
[1] "oro.nifti"
Summaries
How many values actually are greater than zero? Here, we can use standard statistical functions, such as sum
to count the number of TRUE
indices:
sum(above_zero)
[1] 513
and similarly find the proportion of TRUE
indices by taking the mean
of these indicators:
mean(above_zero)
[1] 0.513
Again, as nifti
is an S4 object, it should have the functionality described in the details of the help file for methods::S4groupGeneric
:
min(nim)
[1] -3.517075
max(nim)
[1] 2.706524
range(nim)
[1] -3.517075 2.706524
class(abs(nim))
[1] "nifti"
attr(,"package")
[1] "oro.nifti"
Visualization of nifti
objects
Here we will use real imaging data from the EveTemplate
package:
library(EveTemplate)
eve = readEve(what = "Brain")
Orthographic view
The oro.nifti::orthographic
function provides great functionality on displaying nifti
objects in 3 different planes.
oro.nifti::orthographic(eve)
The neurobase::ortho2
function expands upon this with some different defaults.
neurobase::ortho2(eve)
We see that in ortho2
there are annotations of the orientation of the image. Again, if the image was not reoriented, then these many not be corrrect. You can turn these off with the add.orient
argument:
neurobase::ortho2(eve, add.orient = FALSE)