Simple Image Input/Output

library(neurobase)

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 reading 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.

Downloading an Image

Let’s say you have a T1-weighted NIfTI on your machine. If you do not, we can download one. We are downloading this to a temporary file since we don’t need it after the session is over:

t1_file = tempfile(fileext = ".nii.gz")
url = paste0("https://johnmuschelli.com/", 
                       "open_ms_data/", "cross_sectional/",
                       "raw/patient01/T1W.nii.gz")
download.file(url, destfile = t1_file)
t1_file
#> [1] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp61iZtG/file509017a5b571.nii.gz"

Reading in an image

Here we will use the readnii function to read in the image to an object called img:

library(neurobase)
img = readnii(t1_file)
img
#> NIfTI-1 format
#>   Type            : nifti
#>   Data Type       : 4 (INT16)
#>   Bits per Pixel  : 16
#>   Slice Code      : 0 (Unknown)
#>   Intent Code     : 0 (None)
#>   Qform Code      : 2 (Aligned_Anat)
#>   Sform Code      : 1 (Scanner_Anat)
#>   Dimension       : 408 x 512 x 152
#>   Pixel Dimension : 0.43 x 0.43 x 0.82
#>   Voxel Units     : mm
#>   Time Units      : Unknown

We see the output is a nifti object. We can think of this as an array with additional information (called a header). We can do simple operations on the image, such as sum:

sum(img)
#> [1] 4073996312
sum(img > 1000)
#> [1] 278650

Writing Images

Let’s say we want to set all values greater than 1000 to 1000 and then write the image out. We will use the writenii function. We will copy img to img2 because we want to keep img as is. Again we will write the image to a temporary file because we don’t need this after the session:

img2 = img
img2[ img2 > 1000] = 1000
outfile = tempfile(fileext = ".nii.gz")
writenii(img2, outfile)
file.exists(outfile)
#> [1] TRUE

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 img are greater than 0, we can simply write:

above_zero = img > 0
class(above_zero)
#> [1] "nifti"
#> attr(,"package")
#> [1] "oro.nifti"
img_data(above_zero)[1]
#> [1] FALSE

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(img > 0 & img < 2)
#> [1] "nifti"
#> attr(,"package")
#> [1] "oro.nifti"

Arithmetic on nifti objects

We can also show the

class(img * 2)
#> [1] "nifti"
#> attr(,"package")
#> [1] "oro.nifti"
class(img + (img / 4))
#> [1] "nifti"
#> attr(,"package")
#> [1] "oro.nifti"
class(img * img)
#> [1] "nifti"
#> attr(,"package")
#> [1] "oro.nifti"
class(img^2)
#> [1] "nifti"
#> attr(,"package")
#> [1] "oro.nifti"

Visualization of nifti objects

Orthographic view

The neurobase::ortho2 function expands the oro.nifti::orthographic function for displaying nifti objects in 3 different planes:

neurobase::ortho2(img)

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 correct. You can turn these off with the add.orient argument:

neurobase::ortho2(img, add.orient = FALSE)

Single slice view

We may want to view a single slice of an image. The oro.nifti::slice function can be used here.

oro.nifti::slice(img, z = 45)

We can also get a view of multiple slices:

oro.nifti::slice(img, z = c(45, 50))

Different Planes

We can specify z the same way but change the plane to be different to get a different slice of the brain:

oro.nifti::slice(img, z = 52, plane = "sagittal")

We can similarly do the same for “coronal” slices.

Overlaying slices

We can also overlay one slice of an image upon another using the oro.nifti::overlay function. Here we must specify plot.type for only one slice.

overlay(img, y = img > quantile(img, 0.9), z = 45, plot.type = "single", 
        NA.y = TRUE)