Processing math: 100%
  • Goal
    • Installing Packages
    • Using TCIApathfinder
    • Converting DICOM to NIfTI
    • Skull Strip
    • Registration

Goal

In this tutorial, we will discuss skull-stripping (or brain-extracting) X-ray computed tomography (CT) scans. We will use data from TCIA (http://www.cancerimagingarchive.net/). The entire pipeline goes from raw DICOM data, converts it to NIfTI images, performs brain extraction, and then spatially normalizes the brain to a template using non-linear registration. All of the packages are open source and are available through CRAN or Neuroconductor (https://neuroconductor.org/) for the R programming language. We extract data from TCIA from the TCIApathfinder R package.

Installing Packages

In order to run all the code in this tutorial, these packages need to be installed. The following code should install all the packages.

install.packages(c("TCIApathfinder", "dplyr"))
source("https://neuroconductor.org/neurocLite.R") 
neuro_install(c("dcm2niir", "ichseg", "fslr", "extrantsr"))

Using TCIApathfinder

In order to use TCIApathfinder, please see the vignette to obtain API keys (Russell et al. 2018). Here we will look at the collections of data available givent the code below:

library(TCIApathfinder)
library(dplyr)

Attaching package: 'dplyr'
The following object is masked from 'package:oro.nifti':

    slice
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
series_instance_uid = "1.3.6.1.4.1.14519.5.2.1.2857.3707.893926543922125108620513439908"
download_unzip_series = function(series_instance_uid,
                                 verbose = TRUE) {
  tdir = tempfile()
  dir.create(tdir, recursive = TRUE)
  tfile = tempfile(fileext = ".zip")
  tfile = basename(tfile)
  if (verbose) {
    message("Downloading Series")
  }
  res = save_image_series(
    series_instance_uid = series_instance_uid, 
    out_dir = tdir, 
    out_file_name = tfile)
  if (verbose) {
    message("Unzipping Series")
  }  
  stopifnot(file.exists(res$out_file))
  tdir = tempfile()
  dir.create(tdir, recursive = TRUE)
  res = unzip(zipfile = res$out_file, exdir = tdir)
  L = list(files = res,
           dirs = unique(dirname(normalizePath(res))))
  return(L)
}
# Download and unzip the image series

file_list = download_unzip_series(
  series_instance_uid = series_instance_uid)
Downloading Series
Unzipping Series

Here we extracted a single series of a CT brain scan. The data are in DICOM format.

Converting DICOM to NIfTI

We will use dcm2niix to convert the data from DICOM to NIfTI. The function dcm2niix is wrapped in the dcm2niir R package (Muschelli 2018). We will use dcm2niir::dcm2nii to convert the file. We use check_dcm2nii to grab the relevant output files:

library(dcm2niir)
dcm_result = dcm2nii(file_list$dirs)
#Copying Files
# Converting to nii 
'/Library/Frameworks/R.framework/Versions/3.6/Resources/library/dcm2niir/dcm2niix' -9 -z y -f %p_%t_%s '/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e86ff1b312'
dcm_result$nii_after
[1] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e86ff1b312/HEAD_STD_20010124161800_2_Tilt_1.nii.gz"
[2] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e86ff1b312/HEAD_STD_20010124161800_2.nii.gz"       
result = check_dcm2nii(dcm_result)
result
[1] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e86ff1b312/HEAD_STD_20010124161800_2_Tilt_1.nii.gz"
attr(,"json_file")
[1] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e86ff1b312/HEAD_STD_20010124161800_2.json"

Here we see the output is a single NIfTI file. If there is any gantry tilt or variable slice thickness, dcm2niix has accounted for this.

Next we read the data into R into a nifti object:

library(neurobase)
img = readnii(result)
ortho2(img)

range(img)
[1] -3024  3071

Here we will use neurobase::rescale_img to make sure the minimum is 1024 and the maximum is 3071. The minimum can be lower for areas outside the field of view (FOV). Here we plot the image and the Winsorized version to see the brain tissue:

img = rescale_img(img, min.val = -1024, max.val = 3071)
ortho2(img)

ortho2(img, window = c(0, 100))

We see the image has high resolution within the axial plane, but not as high resolution in the sagittal plane. We see high values in the skull and other dense areas and lower values within the brain and the darkest values outside of the head.

Skull Strip

We can skull strip the image using CT_Skull_Strip or CT_Skull_Stripper from the ichseg R package. The CT_Skull_Stripper has a simple switch to use CT_Skull_Strip or CT_Skull_Strip_robust (Muschelli 2019b).

library(ichseg)
ss = CT_Skull_Strip(img, verbose = FALSE)
Warning in get.fsl(): Setting fsl.path to /usr/local/fsl
Warning in get.fsloutput(): Can't find FSLOUTPUTTYPE, setting to NIFTI_GZ
ortho2(img, ss > 0, 
       window = c(0, 100),
       col.y = scales::alpha("red", 0.5))

The CT_Skull_Strip_robust function does 2 neck removals using remove_neck from extrantsr and then find the center of gravity (COG) twice to make sure the segmentation focuses on the head, which uses some FSL (Jenkinson et al. 2012) functions in the fslr package (Muschelli et al. 2015). In some instances, the whole neck is included in the scan, such as some of the head-neck studies in TCIA.

Registration

Here we register the image to the template image from Rorden (2012). We will use the registration function from the extrantsr R package (Muschelli 2019a). The extrantsr package uses the ANTsR R package to perform the registration, and simply wraps multiple commands together (Avants 2019). We will use a Symmetric Normalization (SyN) type of registration, which first uses an affine registration, then combines it with a symmetric non-linear diffeomorphism. The output file reg$outfile is the registered image.

template_image = ichseg::ct_template(type = "image")
ortho2(template_image, window = c(0, 100))

reg = extrantsr::registration(
  img, template.file = template_image, 
  typeofTransform = "SyN", 
  interpolator = "Linear")
# Running Registration of file to template
# Applying Registration output is
$fwdtransforms
[1] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e81554bff81Warp.nii.gz"      
[2] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e81554bff80GenericAffine.mat"

$invtransforms
[1] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e81554bff80GenericAffine.mat" 
[2] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e81554bff81InverseWarp.nii.gz"

$prev_transforms
character(0)
# Applying Transformations to file
 [1] "-d"                                                                                             
 [2] "3"                                                                                              
 [3] "-i"                                                                                             
 [4] "<pointer: 0x7fcac8fe1c20>"                                                                      
 [5] "-o"                                                                                             
 [6] "<pointer: 0x7fcac8fb3c70>"                                                                      
 [7] "-r"                                                                                             
 [8] "<pointer: 0x7fcab4c0b2f0>"                                                                      
 [9] "-n"                                                                                             
[10] "linear"                                                                                         
[11] "-t"                                                                                             
[12] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e81554bff81Warp.nii.gz"      
[13] "-t"                                                                                             
[14] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e81554bff80GenericAffine.mat"
# Writing out file
[1] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e86c15f438.nii.gz"
# Reading data back into R
wimg = window_img(reg$outfile, window = c(0, 100))
double_ortho(template_image, wimg, window = c(0, 100))

We see relatively good alignment between the template image (left) and the registered image (right)

Here we will use the skull-stripped template and perform the same registration with the skull-stripped image.

template_brain = ichseg::ct_template(type = "brain")
ortho2(template_brain, window = c(0, 100))

brain_reg = extrantsr::registration(
  ss, template.file = template_brain, 
  typeofTransform = "SyN", 
  interpolator = "Linear")
# Running Registration of file to template
# Applying Registration output is
$fwdtransforms
[1] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e87a2ad82c1Warp.nii.gz"      
[2] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e87a2ad82c0GenericAffine.mat"

$invtransforms
[1] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e87a2ad82c0GenericAffine.mat" 
[2] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e87a2ad82c1InverseWarp.nii.gz"

$prev_transforms
character(0)
# Applying Transformations to file
 [1] "-d"                                                                                             
 [2] "3"                                                                                              
 [3] "-i"                                                                                             
 [4] "<pointer: 0x7fcab4e8ad90>"                                                                      
 [5] "-o"                                                                                             
 [6] "<pointer: 0x7fcad0a6c660>"                                                                      
 [7] "-r"                                                                                             
 [8] "<pointer: 0x7fcab4e46050>"                                                                      
 [9] "-n"                                                                                             
[10] "linear"                                                                                         
[11] "-t"                                                                                             
[12] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e87a2ad82c1Warp.nii.gz"      
[13] "-t"                                                                                             
[14] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e87a2ad82c0GenericAffine.mat"
# Writing out file
[1] "/var/folders/1s/wrtqcpxn685_zk570bnx9_rr0000gr/T//Rtmp5zn4fm/file90e8141912f2.nii.gz"
# Reading data back into R
wbrain = window_img(brain_reg$outfile, window = c(0, 100))
double_ortho(template_image, wbrain, window = c(0, 100))

We see again good alignment, but we see that there are some stark differences in these registrations when we compare them:

double_ortho(wimg, wbrain)

Avants, Brian B. 2019. ANTsR: ANTs in R: Quantification Tools for Biomedical Images.

Jenkinson, Mark, Christian F Beckmann, Timothy EJ Behrens, Mark W Woolrich, and Stephen M Smith. 2012. “FSL.” NeuroImage 62 (2): 782–90.

Muschelli, John. 2018. dcm2niir: Conversion of DICOM to NIfTI Imaging Files Through R. https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage.

———. 2019a. extrantsr: Extra Functions to Build on the ANTsR Package.

———. 2019b. ichseg: Intracerebral Hemorrhage Segmentation of X-Ray Computed Tomography (CT) Images.

Muschelli, John, Elizabeth Sweeney, Martin Lindquist, and Ciprian Crainiceanu. 2015. “fslr: Connecting the FSL Software with R.” The R Journal 7 (1): 163–75.

Russell, Pamela, Kelly Fountain, Dulcy Wolverton, and Debashis Ghosh. 2018. “TCIApathfinder: An R Client for the Cancer Imaging Archive REST API.” Cancer Research 78 (15): 4424–6.

LS0tCnRpdGxlOiAiU2t1bGwgU3RyaXBwaW5nIGFuZCBSZWdpc3RyYXRpb24gb2YgSGVhZCBDVCBEYXRhIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGtlZXBfbWQ6IHllcwogICAgY29kZV9kb3dubG9hZDogdHJ1ZSAgICAKICAgIHRoZW1lOiBjb3NtbwogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IG5vCiAgcGRmX2RvY3VtZW50OgogICAga2VlcF90ZXg6IGZhbHNlCmJpYmxpb2dyYXBoeTogcmVmcy5iaWIKLS0tCgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkoZGNtMm5paXIpCmxpYnJhcnkoaWNoc2VnKQpsaWJyYXJ5KGZzbHIpCmxpYnJhcnkoZXh0cmFudHNyKQpsaWJyYXJ5KFRDSUFwYXRoZmluZGVyKQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGNvbW1lbnQgPSAiIikKYGBgCgoKIyBHb2FsCkluIHRoaXMgdHV0b3JpYWwsIHdlIHdpbGwgZGlzY3VzcyBza3VsbC1zdHJpcHBpbmcgKG9yIGJyYWluLWV4dHJhY3RpbmcpIFgtcmF5IGNvbXB1dGVkIHRvbW9ncmFwaHkgKENUKSBzY2Fucy4gIFdlIHdpbGwgdXNlIGRhdGEgZnJvbSBUQ0lBIChodHRwOi8vd3d3LmNhbmNlcmltYWdpbmdhcmNoaXZlLm5ldC8pLiAgVGhlIGVudGlyZSBwaXBlbGluZSBnb2VzIGZyb20gcmF3IERJQ09NIGRhdGEsIGNvbnZlcnRzIGl0IHRvIE5JZlRJIGltYWdlcywgcGVyZm9ybXMgYnJhaW4gZXh0cmFjdGlvbiwgYW5kIHRoZW4gc3BhdGlhbGx5IG5vcm1hbGl6ZXMgdGhlIGJyYWluIHRvIGEgdGVtcGxhdGUgdXNpbmcgbm9uLWxpbmVhciByZWdpc3RyYXRpb24uICBBbGwgb2YgdGhlIHBhY2thZ2VzIGFyZSBvcGVuIHNvdXJjZSBhbmQgYXJlIGF2YWlsYWJsZSB0aHJvdWdoIFtDUkFOXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pIG9yIFtOZXVyb2NvbmR1Y3RvciAoaHR0cHM6Ly9uZXVyb2NvbmR1Y3Rvci5vcmcvKV0oaHR0cHM6Ly9uZXVyb2NvbmR1Y3Rvci5vcmcvKSBmb3IgdGhlIFIgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UuICBXZSBleHRyYWN0IGRhdGEgZnJvbSBUQ0lBIGZyb20gdGhlIFtgVENJQXBhdGhmaW5kZXJgXShodHRwczovL0NSQU4uUi1wcm9qZWN0Lm9yZy9wYWNrYWdlPVRDSUFwYXRoZmluZGVyKSBSIHBhY2thZ2UuCgojIyBJbnN0YWxsaW5nIFBhY2thZ2VzCgpJbiBvcmRlciB0byBydW4gYWxsIHRoZSBjb2RlIGluIHRoaXMgdHV0b3JpYWwsIHRoZXNlIHBhY2thZ2VzIG5lZWQgdG8gYmUgaW5zdGFsbGVkLiAgVGhlIGZvbGxvd2luZyBjb2RlIHNob3VsZCBpbnN0YWxsIGFsbCB0aGUgcGFja2FnZXMuIAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KaW5zdGFsbC5wYWNrYWdlcyhjKCJUQ0lBcGF0aGZpbmRlciIsICJkcGx5ciIpKQpzb3VyY2UoImh0dHBzOi8vbmV1cm9jb25kdWN0b3Iub3JnL25ldXJvY0xpdGUuUiIpIApuZXVyb19pbnN0YWxsKGMoImRjbTJuaWlyIiwgImljaHNlZyIsICJmc2xyIiwgImV4dHJhbnRzciIpKQpgYGAKCgoKIyMgVXNpbmcgVENJQXBhdGhmaW5kZXIKCkluIG9yZGVyIHRvIHVzZSBgVENJQXBhdGhmaW5kZXJgLCBwbGVhc2Ugc2VlIHRoZSBbdmlnbmV0dGUgdG8gb2J0YWluIEFQSSBrZXlzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvVENJQXBhdGhmaW5kZXIvdmlnbmV0dGVzL2ludHJvZHVjdGlvbi5odG1sKSBbQFRDSUFwYXRoZmluZGVyXS4gIEhlcmUgd2Ugd2lsbCBsb29rIGF0IHRoZSBjb2xsZWN0aW9ucyBvZiBkYXRhIGF2YWlsYWJsZSBnaXZlbnQgdGhlIGNvZGUgYmVsb3c6CgpgYGB7cn0KbGlicmFyeShUQ0lBcGF0aGZpbmRlcikKbGlicmFyeShkcGx5cikKc2VyaWVzX2luc3RhbmNlX3VpZCA9ICIxLjMuNi4xLjQuMS4xNDUxOS41LjIuMS4yODU3LjM3MDcuODkzOTI2NTQzOTIyMTI1MTA4NjIwNTEzNDM5OTA4Igpkb3dubG9hZF91bnppcF9zZXJpZXMgPSBmdW5jdGlvbihzZXJpZXNfaW5zdGFuY2VfdWlkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gVFJVRSkgewogIHRkaXIgPSB0ZW1wZmlsZSgpCiAgZGlyLmNyZWF0ZSh0ZGlyLCByZWN1cnNpdmUgPSBUUlVFKQogIHRmaWxlID0gdGVtcGZpbGUoZmlsZWV4dCA9ICIuemlwIikKICB0ZmlsZSA9IGJhc2VuYW1lKHRmaWxlKQogIGlmICh2ZXJib3NlKSB7CiAgICBtZXNzYWdlKCJEb3dubG9hZGluZyBTZXJpZXMiKQogIH0KICByZXMgPSBzYXZlX2ltYWdlX3NlcmllcygKICAgIHNlcmllc19pbnN0YW5jZV91aWQgPSBzZXJpZXNfaW5zdGFuY2VfdWlkLCAKICAgIG91dF9kaXIgPSB0ZGlyLCAKICAgIG91dF9maWxlX25hbWUgPSB0ZmlsZSkKICBpZiAodmVyYm9zZSkgewogICAgbWVzc2FnZSgiVW56aXBwaW5nIFNlcmllcyIpCiAgfSAgCiAgc3RvcGlmbm90KGZpbGUuZXhpc3RzKHJlcyRvdXRfZmlsZSkpCiAgdGRpciA9IHRlbXBmaWxlKCkKICBkaXIuY3JlYXRlKHRkaXIsIHJlY3Vyc2l2ZSA9IFRSVUUpCiAgcmVzID0gdW56aXAoemlwZmlsZSA9IHJlcyRvdXRfZmlsZSwgZXhkaXIgPSB0ZGlyKQogIEwgPSBsaXN0KGZpbGVzID0gcmVzLAogICAgICAgICAgIGRpcnMgPSB1bmlxdWUoZGlybmFtZShub3JtYWxpemVQYXRoKHJlcykpKSkKICByZXR1cm4oTCkKfQojIERvd25sb2FkIGFuZCB1bnppcCB0aGUgaW1hZ2Ugc2VyaWVzCgpmaWxlX2xpc3QgPSBkb3dubG9hZF91bnppcF9zZXJpZXMoCiAgc2VyaWVzX2luc3RhbmNlX3VpZCA9IHNlcmllc19pbnN0YW5jZV91aWQpCmBgYAoKSGVyZSB3ZSBleHRyYWN0ZWQgYSBzaW5nbGUgc2VyaWVzIG9mIGEgQ1QgYnJhaW4gc2Nhbi4gIFRoZSBkYXRhIGFyZSBpbiBESUNPTSBmb3JtYXQuCgojIyBDb252ZXJ0aW5nIERJQ09NIHRvIE5JZlRJCgpXZSB3aWxsIHVzZSBbYGRjbTJuaWl4YF0oaHR0cHM6Ly9naXRodWIuY29tL3JvcmRlbmxhYi9kY20ybmlpeCkgdG8gY29udmVydCB0aGUgZGF0YSBmcm9tIERJQ09NIHRvIE5JZlRJLiAgVGhlIGZ1bmN0aW9uIGBkY20ybmlpeGAgaXMgd3JhcHBlZCBpbiB0aGUgYGRjbTJuaWlyYCBSIHBhY2thZ2UgW0BkY20ybmlpcl0uICBXZSB3aWxsIHVzZSBgZGNtMm5paXI6OmRjbTJuaWlgIHRvIGNvbnZlcnQgdGhlIGZpbGUuICBXZSB1c2UgYGNoZWNrX2RjbTJuaWlgIHRvIGdyYWIgdGhlIHJlbGV2YW50IG91dHB1dCBmaWxlczoKIApgYGB7cn0KbGlicmFyeShkY20ybmlpcikKZGNtX3Jlc3VsdCA9IGRjbTJuaWkoZmlsZV9saXN0JGRpcnMpCmRjbV9yZXN1bHQkbmlpX2FmdGVyCnJlc3VsdCA9IGNoZWNrX2RjbTJuaWkoZGNtX3Jlc3VsdCkKcmVzdWx0CmBgYApIZXJlIHdlIHNlZSB0aGUgb3V0cHV0IGlzIGEgc2luZ2xlIE5JZlRJIGZpbGUuICBJZiB0aGVyZSBpcyBhbnkgZ2FudHJ5IHRpbHQgb3IgdmFyaWFibGUgc2xpY2UgdGhpY2tuZXNzLCBgZGNtMm5paXhgIGhhcyBhY2NvdW50ZWQgZm9yIHRoaXMuICAKCk5leHQgd2UgcmVhZCB0aGUgZGF0YSBpbnRvIGBSYCBpbnRvIGEgYG5pZnRpYCBvYmplY3Q6CmBgYHtyfQpsaWJyYXJ5KG5ldXJvYmFzZSkKaW1nID0gcmVhZG5paShyZXN1bHQpCm9ydGhvMihpbWcpCnJhbmdlKGltZykKYGBgCgpIZXJlIHdlIHdpbGwgdXNlIGBuZXVyb2Jhc2U6OnJlc2NhbGVfaW1nYCB0byBtYWtlIHN1cmUgdGhlIG1pbmltdW0gaXMgJC0xMDI0JCBhbmQgdGhlIG1heGltdW0gaXMgJDMwNzEkLiAgVGhlIG1pbmltdW0gY2FuIGJlIGxvd2VyIGZvciBhcmVhcyBvdXRzaWRlIHRoZSBmaWVsZCBvZiB2aWV3IChGT1YpLiAgSGVyZSB3ZSBwbG90IHRoZSBpbWFnZSBhbmQgdGhlIFdpbnNvcml6ZWQgdmVyc2lvbiB0byBzZWUgdGhlIGJyYWluIHRpc3N1ZToKCmBgYHtyfQppbWcgPSByZXNjYWxlX2ltZyhpbWcsIG1pbi52YWwgPSAtMTAyNCwgbWF4LnZhbCA9IDMwNzEpCm9ydGhvMihpbWcpCm9ydGhvMihpbWcsIHdpbmRvdyA9IGMoMCwgMTAwKSkKYGBgCgpXZSBzZWUgdGhlIGltYWdlIGhhcyBoaWdoIHJlc29sdXRpb24gd2l0aGluIHRoZSBheGlhbCBwbGFuZSwgYnV0IG5vdCBhcyBoaWdoIHJlc29sdXRpb24gaW4gdGhlIHNhZ2l0dGFsIHBsYW5lLiAgV2Ugc2VlIGhpZ2ggdmFsdWVzIGluIHRoZSBza3VsbCBhbmQgb3RoZXIgZGVuc2UgYXJlYXMgYW5kIGxvd2VyIHZhbHVlcyB3aXRoaW4gdGhlIGJyYWluIGFuZCB0aGUgZGFya2VzdCB2YWx1ZXMgb3V0c2lkZSBvZiB0aGUgaGVhZC4KCiMjIFNrdWxsIFN0cmlwCgpXZSBjYW4gc2t1bGwgc3RyaXAgdGhlIGltYWdlIHVzaW5nIGBDVF9Ta3VsbF9TdHJpcGAgb3IgYENUX1NrdWxsX1N0cmlwcGVyYCBmcm9tIHRoZSBgaWNoc2VnYCBSIHBhY2thZ2UuICBUaGUgYENUX1NrdWxsX1N0cmlwcGVyYCBoYXMgYSBzaW1wbGUgc3dpdGNoIHRvIHVzZSBgQ1RfU2t1bGxfU3RyaXBgIG9yIGBDVF9Ta3VsbF9TdHJpcF9yb2J1c3RgIFtAaWNoc2VnXS4gIApgYGB7cn0KbGlicmFyeShpY2hzZWcpCnNzID0gQ1RfU2t1bGxfU3RyaXAoaW1nLCB2ZXJib3NlID0gRkFMU0UpCm9ydGhvMihpbWcsIHNzID4gMCwgCiAgICAgICB3aW5kb3cgPSBjKDAsIDEwMCksCiAgICAgICBjb2wueSA9IHNjYWxlczo6YWxwaGEoInJlZCIsIDAuNSkpCmBgYAoKVGhlIGBDVF9Ta3VsbF9TdHJpcF9yb2J1c3RgIGZ1bmN0aW9uIGRvZXMgMiBuZWNrIHJlbW92YWxzIHVzaW5nIGByZW1vdmVfbmVja2AgZnJvbSBgZXh0cmFudHNyYCBhbmQgdGhlbiBmaW5kIHRoZSBjZW50ZXIgb2YgZ3Jhdml0eSAoQ09HKSB0d2ljZSB0byBtYWtlIHN1cmUgdGhlIHNlZ21lbnRhdGlvbiBmb2N1c2VzIG9uIHRoZSBoZWFkLCB3aGljaCB1c2VzIHNvbWUgRlNMIFtAZnNsXSBmdW5jdGlvbnMgaW4gdGhlIGBmc2xyYCBwYWNrYWdlIFtAZnNscl0uICBJbiBzb21lIGluc3RhbmNlcywgdGhlIHdob2xlIG5lY2sgaXMgaW5jbHVkZWQgaW4gdGhlIHNjYW4sIHN1Y2ggYXMgc29tZSBvZiB0aGUgaGVhZC1uZWNrIHN0dWRpZXMgaW4gVENJQS4KCiMjIFJlZ2lzdHJhdGlvbgoKSGVyZSB3ZSByZWdpc3RlciB0aGUgaW1hZ2UgdG8gdGhlIHRlbXBsYXRlIGltYWdlIGZyb20gUm9yZGVuICgyMDEyKS4gIFdlIHdpbGwgdXNlIHRoZSBgcmVnaXN0cmF0aW9uYCBmdW5jdGlvbiBmcm9tIHRoZSBgZXh0cmFudHNyYCBSIHBhY2thZ2UgW0BleHRyYW50c3JdLiAgVGhlIGBleHRyYW50c3JgIHBhY2thZ2UgdXNlcyB0aGUgYEFOVHNSYCBSIHBhY2thZ2UgdG8gcGVyZm9ybSB0aGUgcmVnaXN0cmF0aW9uLCBhbmQgc2ltcGx5IHdyYXBzIG11bHRpcGxlIGNvbW1hbmRzIHRvZ2V0aGVyIFtAQU5Uc1JdLiAgV2Ugd2lsbCB1c2UgYSBTeW1tZXRyaWMgTm9ybWFsaXphdGlvbiAoU3lOKSB0eXBlIG9mIHJlZ2lzdHJhdGlvbiwgd2hpY2ggZmlyc3QgdXNlcyBhbiBhZmZpbmUgcmVnaXN0cmF0aW9uLCB0aGVuIGNvbWJpbmVzIGl0IHdpdGggYSBzeW1tZXRyaWMgbm9uLWxpbmVhciBkaWZmZW9tb3JwaGlzbS4gIFRoZSBvdXRwdXQgZmlsZSBgcmVnJG91dGZpbGVgIGlzIHRoZSByZWdpc3RlcmVkIGltYWdlLgoKYGBge3J9CnRlbXBsYXRlX2ltYWdlID0gaWNoc2VnOjpjdF90ZW1wbGF0ZSh0eXBlID0gImltYWdlIikKb3J0aG8yKHRlbXBsYXRlX2ltYWdlLCB3aW5kb3cgPSBjKDAsIDEwMCkpCnJlZyA9IGV4dHJhbnRzcjo6cmVnaXN0cmF0aW9uKAogIGltZywgdGVtcGxhdGUuZmlsZSA9IHRlbXBsYXRlX2ltYWdlLCAKICB0eXBlb2ZUcmFuc2Zvcm0gPSAiU3lOIiwgCiAgaW50ZXJwb2xhdG9yID0gIkxpbmVhciIpCndpbWcgPSB3aW5kb3dfaW1nKHJlZyRvdXRmaWxlLCB3aW5kb3cgPSBjKDAsIDEwMCkpCmRvdWJsZV9vcnRobyh0ZW1wbGF0ZV9pbWFnZSwgd2ltZywgd2luZG93ID0gYygwLCAxMDApKQpgYGAKV2Ugc2VlIHJlbGF0aXZlbHkgZ29vZCBhbGlnbm1lbnQgYmV0d2VlbiB0aGUgdGVtcGxhdGUgaW1hZ2UgKGxlZnQpIGFuZCB0aGUgcmVnaXN0ZXJlZCBpbWFnZSAocmlnaHQpCgpIZXJlIHdlIHdpbGwgdXNlIHRoZSBza3VsbC1zdHJpcHBlZCB0ZW1wbGF0ZSBhbmQgcGVyZm9ybSB0aGUgc2FtZSByZWdpc3RyYXRpb24gd2l0aCB0aGUgc2t1bGwtc3RyaXBwZWQgaW1hZ2UuICAKYGBge3J9CnRlbXBsYXRlX2JyYWluID0gaWNoc2VnOjpjdF90ZW1wbGF0ZSh0eXBlID0gImJyYWluIikKb3J0aG8yKHRlbXBsYXRlX2JyYWluLCB3aW5kb3cgPSBjKDAsIDEwMCkpCmJyYWluX3JlZyA9IGV4dHJhbnRzcjo6cmVnaXN0cmF0aW9uKAogIHNzLCB0ZW1wbGF0ZS5maWxlID0gdGVtcGxhdGVfYnJhaW4sIAogIHR5cGVvZlRyYW5zZm9ybSA9ICJTeU4iLCAKICBpbnRlcnBvbGF0b3IgPSAiTGluZWFyIikKd2JyYWluID0gd2luZG93X2ltZyhicmFpbl9yZWckb3V0ZmlsZSwgd2luZG93ID0gYygwLCAxMDApKQpkb3VibGVfb3J0aG8odGVtcGxhdGVfaW1hZ2UsIHdicmFpbiwgd2luZG93ID0gYygwLCAxMDApKQpgYGAKCldlIHNlZSBhZ2FpbiBnb29kIGFsaWdubWVudCwgYnV0IHdlIHNlZSB0aGF0IHRoZXJlIGFyZSBzb21lIHN0YXJrIGRpZmZlcmVuY2VzIGluIHRoZXNlIHJlZ2lzdHJhdGlvbnMgd2hlbiB3ZSBjb21wYXJlIHRoZW06CgpgYGB7cn0KZG91YmxlX29ydGhvKHdpbWcsIHdicmFpbikKYGBgCg==