Diff of /R/CT_Skull_Strip_robust.R [000000] .. [242173]

Switch to unified view

a b/R/CT_Skull_Strip_robust.R
1
#' @title Robust CT Skull Stripping
2
#' @description Skull Stripping (using FSL's BET) a CT file
3
#' using \code{fslr}
4
#' functions and robustified by registration
5
#' @param img (character) File to be skull stripped or object of class
6
#' nifti
7
#' @param outfile (character) output filename
8
#' @param keepmask (logical) Should we keep the mask?
9
#' @param maskfile (character) Filename for mask
10
#' (if \code{keepmask = TRUE}).
11
#' If \code{NULL}, then will do \code{paste0(outfile, "_Mask")}.
12
#' @param retimg (logical) return image of class nifti
13
#' @param reorient (logical) If retimg, should file be
14
#' reoriented when read in?
15
#' Passed to \code{\link{readNIfTI}}.
16
#' @param int Fractional Intensity passed to
17
#' \code{\link{CT_Skull_Strip}} and
18
#' subsequently \code{\link{fslbet}}.
19
#' @param lthresh (default: 0) Lower value to threshold CT
20
#' \code{\link{fslthresh}}
21
#' @param uthresh (default: 100) Upper value to threshold CT
22
#' \code{\link{fslthresh}}
23
#' @param nvoxels Number of voxels to dilate/erode.
24
#' See \code{\link{dil_ero}}.
25
#' If \code{nvoxels = 0}, then no smoothing is done.
26
#' @param remove.neck Run \code{\link{remove_neck}} to register
27
#' the template to a
28
#' thresholded image to remove neck slices.
29
#' @param remover if \code{remove.neck = TRUE}, then which function
30
#' would you like to use to remove the neck
31
#' @param smooth.factor Smoothing factor for \code{\link{fslbet}}.
32
#' See \code{-w}
33
#' option in \code{fslbet.help()}.
34
#' @param verbose (logical) Should diagnostic output be printed?
35
#' @param opts Not used
36
#' @param template.file Template to warp to original image
37
#' space, passed to
38
#' \code{\link{remove_neck}}
39
#' @param template.mask Mask of template to use as rough
40
#' brain mask, passed
41
#' to \code{\link{remove_neck}}
42
#' @param ... additional arguments passed to
43
#' \code{\link{CT_Skull_Strip}} or
44
#' \code{\link{remove_neck}}.
45
#' @param recog Re-estimate the center of gravity (COG) and
46
#' skull strip.
47
#' @param mask_to_background When masking, should the values outside the
48
#' mask be set to 0 (default) or -1024 (when TRUE)
49
#' @return Skull-stripped \code{nifti} object
50
#' @note This function first thresholds an image, runs a rigid
51
#' registration
52
#' (default in \code{\link{remove_neck}}) to drop any slices
53
#' below the transformed
54
#' skull stripped template to remove neck slices.  The neck-removed
55
#' image is
56
#' then skull stripped using defaults in \code{\link{CT_Skull_Strip}}.
57
#'  A new
58
#' center of gravity is estiamted using \code{\link{cog}}, then the
59
#' image is
60
#' skull stripped again using the new cog and the smoothness factor
61
#' (passed to \code{-w} argument in BET). After the skull stripped
62
#' mask is
63
#' created, the image is dilated and eroded using
64
#' \code{\link{dil_ero}} to
65
#' fill holes using a box kernel with the number of voxels
66
#' \code{nvoxels} in
67
#' all 3 directions.
68
#' @importFrom extrantsr remove_neck double_remove_neck
69
#' @importFrom neurobase check_outfile readnii writenii cog
70
#' @importFrom oro.nifti readNIfTI drop_img_dim
71
#' @export
72
CT_Skull_Strip_robust <- function(
73
  img,
74
  outfile = NULL,
75
  keepmask = TRUE,
76
  maskfile = NULL,
77
  retimg = TRUE,
78
  reorient = FALSE,
79
  int = "0.01",
80
  lthresh = 0,
81
  uthresh = 100,
82
  nvoxels = 5,
83
  remove.neck = TRUE,
84
  remover = c("remove_neck", "double_remove_neck"),
85
  smooth.factor = 2,
86
  recog = TRUE,
87
  verbose = TRUE,
88
  opts = NULL,
89
  mask_to_background = FALSE,
90
  template.file =
91
    system.file("scct_unsmooth_SS_0.01.nii.gz",
92
                package = "ichseg"),
93
  template.mask =
94
    system.file("scct_unsmooth_SS_0.01_Mask.nii.gz",
95
                package = "ichseg"),
96
  ...
97
){
98
99
  ###########################################
100
  # Checking outfile input/output
101
  ###########################################
102
  outfile = check_outfile(outfile = outfile,
103
                          retimg = retimg,
104
                          fileext = "")
105
  outfile = path.expand(outfile)
106
  tfile = tempfile()
107
108
  ### Working on maskfile
109
  if (is.null(maskfile)){
110
    maskfile = nii.stub(outfile)
111
    maskfile = paste0(maskfile, "_Mask")
112
  }
113
  maskfile = nii.stub(maskfile)
114
  stopifnot(inherits(maskfile, "character"))
115
116
  #############################
117
  # Threshold Image
118
  #############################
119
  img = check_nifti(img, reorient = reorient)
120
  thresh_img = niftiarr(img, img * (img > lthresh & img < uthresh))
121
  if (!any(c(thresh_img) > 0)) {
122
    stop("No positive values in the thresholded output!")
123
  }
124
125
  thresh_img = drop_img_dim(thresh_img)
126
  thresh = checkimg(thresh_img)
127
  rm(thresh_img)
128
  ## need to do rep.value = 0 because template is like that.
129
130
  #############################
131
  # Removing Neck
132
  #############################
133
  if (remove.neck) {
134
    if (verbose) {
135
      message(paste0("# Removing Neck\n"))
136
    }
137
    L = list(
138
      file = thresh,
139
      template.file = template.file,
140
      template.mask = template.mask,
141
      rep.value = 0,
142
      verbose = verbose,
143
      ret_mask = TRUE,
144
      swapdim = TRUE,
145
      ...)
146
    remover = match.arg(remover)
147
    neck_mask = do.call(remover, args = L)
148
    # neck_mask = remove_neck(thresh,
149
    #                         rep.value = 0,
150
    #                         template.file = template.file,
151
    #                         template.mask = template.mask,
152
    #                         ret_mask = TRUE,
153
    #                         swapdim = TRUE,
154
    #                         verbose = verbose,
155
    #                         ...)
156
  } else {
157
    neck_mask = niftiarr(img, array(1, dim = dim(img)))
158
  }
159
  add_value = 0
160
  if (mask_to_background) {
161
    add_value = 1024
162
  }
163
  noneck = mask_img(img + add_value, neck_mask) - add_value
164
  noneck = drop_img_dim(noneck)
165
  noneck = checkimg(noneck)
166
167
  rm(neck_mask)
168
  #############################
169
  # Skull Stripping no-neck image
170
  #############################
171
  if (verbose) {
172
    message(paste0("# Skull Stripping for COG\n"))
173
  }
174
  ss = CT_Skull_Strip(noneck, outfile = tfile, retimg = TRUE,
175
                      maskfile = maskfile,
176
                      lthresh = lthresh, uthresh = uthresh,
177
                      opts = paste("-f ", int,
178
                                   ifelse(verbose, " -v", "")),
179
                      verbose = verbose,
180
                      keepmask = TRUE, reorient = reorient, ...)
181
182
  ssmask = readNIfTI(maskfile,
183
                     reorient = reorient)
184
  if (recog) {
185
    #############################
186
    # Setting new center of gravity and rerunning with smoothness factor
187
    #############################
188
    cog = cog(ssmask,
189
              ceil = TRUE)
190
    if (verbose){
191
      message(paste0("# Skull Stripping with new cog\n"))
192
    }
193
    tmp_out = CT_Skull_Strip(noneck, outfile = tfile, retimg = TRUE,
194
                        opts = paste("-f ", int,
195
                                     ifelse(verbose, "-v", ""),
196
                                     "-w ", smooth.factor,
197
                                     paste(c("-c", cog),
198
                                           collapse=" ")),
199
                        maskfile = maskfile,
200
                        keepmask = TRUE,
201
                        reorient = reorient,
202
                        verbose = verbose,
203
                        ...)
204
    rm(tmp_out)
205
    ssmask = readNIfTI(maskfile,
206
                       reorient = reorient)
207
  }
208
209
  #############################
210
  # Filling the mask
211
  #############################
212
  if (nvoxels > 0){
213
    if (verbose) {
214
      message(paste0("# Filling Holes \n"))
215
    }
216
    ssmask = dil_ero(ssmask,
217
                     retimg = TRUE,
218
                     nvoxels = nvoxels,
219
                     verbose = verbose)
220
  }
221
  ss = mask_img(img + add_value, ssmask) - add_value
222
223
  ss = drop_img_dim(ss)
224
225
  writenii(ss,
226
           filename = outfile)
227
228
  #############################
229
  # Removing mask if keepmask = FALSE
230
  #############################
231
  if (!keepmask) {
232
    if (verbose) {
233
      message("# Removing Mask File\n")
234
    }
235
    maskfile = nii.stub(maskfile)
236
    ext = get.imgext()
237
    maskfile = paste0(maskfile, ext)
238
    file.remove(maskfile)
239
  }
240
  if (!retimg) {
241
    outfile = nii.stub(outfile)
242
    ext = get.imgext()
243
    outfile = paste0(outfile, ext)
244
    ss = outfile
245
  }
246
  return(ss)
247
}
248
249
250
251
252
#' @rdname CT_Skull_Strip_robust
253
#' @note \code{CT_Skull_Strip_register} removes the neck, registers the
254
#' image to the template, using a rigid-body transformation,
255
#' runs the skull stripper to get a mask, then transforms the mask
256
#' back to native space.
257
#' @export
258
CT_Skull_Strip_register <- function(
259
  img,
260
  outfile = NULL,
261
  keepmask = TRUE,
262
  maskfile = NULL,
263
  retimg = TRUE,
264
  reorient = FALSE,
265
  lthresh = 0,
266
  uthresh = 100,
267
  remove.neck = TRUE,
268
  remover = c("remove_neck", "double_remove_neck"),
269
  verbose = TRUE,
270
  mask_to_background = FALSE,
271
  ...
272
){
273
274
  ###########################################
275
  # Checking outfile input/output
276
  ###########################################
277
  outfile = check_outfile(outfile = outfile,
278
                          retimg = retimg,
279
                          fileext = "")
280
  outfile = path.expand(outfile)
281
  tfile = tempfile()
282
283
  ### Working on maskfile
284
  if (is.null(maskfile)) {
285
    maskfile = nii.stub(outfile)
286
    maskfile = paste0(maskfile, "_Mask")
287
  }
288
  maskfile = nii.stub(maskfile)
289
  stopifnot(inherits(maskfile, "character"))
290
291
  #############################
292
  # Threshold Image
293
  #############################
294
  if (verbose) {
295
    message(paste0("# Thresholding Image to ",
296
                   lthresh, "-", uthresh, "\n"))
297
  }
298
  img = check_nifti(img, reorient = reorient)
299
  thresh_img = niftiarr(img, img * (img > lthresh & img < uthresh))
300
301
  thresh_img = drop_img_dim(thresh_img)
302
  thresh = checkimg(thresh_img)
303
  rm(thresh_img)
304
  ## need to do rep.value = 0 because template is like that.
305
306
  #############################
307
  # Removing Neck
308
  #############################
309
  template.file =
310
    system.file("scct_unsmooth_SS_0.01.nii.gz",
311
                package = "ichseg")
312
  template.mask =
313
    system.file("scct_unsmooth_SS_0.01_Mask.nii.gz",
314
                package = "ichseg")
315
  if (remove.neck) {
316
    if (verbose) {
317
      message(paste0("# Removing Neck\n"))
318
    }
319
    L = list(
320
      file = thresh,
321
      template.file = template.file,
322
      template.mask = template.mask,
323
      rep.value = 0,
324
      verbose = verbose,
325
      ret_mask = TRUE,
326
      swapdim = TRUE,
327
      ...)
328
    remover = match.arg(remover)
329
    neck_mask = do.call(remover, args = L)
330
    # neck_mask = remove_neck(thresh,
331
    #                         rep.value = 0,
332
    #                         template.file = template.file,
333
    #                         template.mask = template.mask,
334
    #                         ret_mask = TRUE,
335
    #                         swapdim = TRUE,
336
    #                         verbose = verbose,
337
    #                         ...)
338
  } else {
339
    neck_mask = niftiarr(img, array(1, dim = dim(img)))
340
  }
341
  add_value = 0
342
  if (mask_to_background) {
343
    add_value = 1024
344
  }
345
  noneck = check_nifti(img)
346
  noneck = mask_img(noneck + add_value, neck_mask) - add_value
347
  noneck = drop_img_dim(noneck)
348
  noneck = checkimg(noneck)
349
350
  rm(neck_mask)
351
352
  if (verbose) {
353
    message("# Registering to Template")
354
  }
355
  template_with_skull =
356
    system.file("scct_unsmooth.nii.gz",
357
                package = "ichseg")
358
  reg = registration(
359
    noneck,
360
    template.file = template_with_skull,
361
    typeofTransform = "Rigid",
362
    interpolator = "NearestNeighbor",
363
    correct = FALSE,
364
    verbose = verbose)
365
  original_noneck = noneck
366
  noneck = reg$outfile
367
368
  #############################
369
  # Skull Stripping no-neck image
370
  #############################
371
  if (verbose) {
372
    message(paste0("# Skull Stripping in Template Space"))
373
  }
374
  template_ss = CT_Skull_Strip(
375
    noneck, outfile = tfile, retimg = TRUE,
376
    maskfile = maskfile,
377
    lthresh = lthresh, uthresh = uthresh,
378
    verbose = verbose,
379
    keepmask = TRUE, reorient = reorient, ...)
380
  template_ssmask = readnii(maskfile,
381
                            reorient = reorient)
382
383
  if (verbose) {
384
    message("# Inverting Transformation")
385
  }
386
  ssmask = ants_apply_transforms(
387
    fixed = original_noneck,
388
    moving = template_ssmask,
389
    transformlist = reg$fwdtransforms,
390
    whichtoinvert = 1,
391
    interpolator = "NearestNeighbor")
392
  ssmask = neurobase::copyNIfTIHeader(img = img, arr = ssmask)
393
  writenii(ssmask, maskfile)
394
  rm(template_ssmask)
395
396
  ss = mask_img(img + add_value, ssmask) - add_value
397
  ss = drop_img_dim(ss)
398
399
  writenii(ss, filename = outfile)
400
401
  #############################
402
  # Removing mask if keepmask = FALSE
403
  #############################
404
  if (!keepmask) {
405
    if (verbose) {
406
      message("# Removing Mask File\n")
407
    }
408
    maskfile = nii.stub(maskfile)
409
    ext = get.imgext()
410
    maskfile = paste0(maskfile, ext)
411
    file.remove(maskfile)
412
  }
413
  if (!retimg) {
414
    outfile = nii.stub(outfile)
415
    ext = get.imgext()
416
    outfile = paste0(outfile, ext)
417
    ss = outfile
418
  }
419
  return(ss)
420
}
421
422
#' @rdname CT_Skull_Strip_robust
423
#' @export
424
#' @param smooth_before_threshold Should the image be smoothed before
425
#' thresholding?  This can be useful for bone-window scans.
426
#' @param add_1024 Adding 1024 to the image *before* running the skull
427
#' stripping.  The values are subtracted after.  This has interplay with
428
#' \code{mask_to_background}
429
CT_Skull_Strip_smooth = function(
430
  img, ...,
431
  smooth_before_threshold = TRUE,
432
  smooth.factor = 1,
433
  remove.neck = TRUE,
434
  remover = c("remove_neck", "double_remove_neck"),
435
  recog = FALSE,
436
  nvoxels = 0,
437
  add_1024 = FALSE) {
438
  # grab all the args
439
  args = list(...)
440
  args$smooth_before_threshold = smooth_before_threshold
441
  args$smooth.factor = smooth.factor
442
  args$remove.neck = remove.neck
443
  args$remover = match.arg(remover)
444
  args$recog = recog
445
  args$nvoxels = nvoxels
446
447
  # read in the image, and add 1024, adjust the lthresh/uthresh
448
  if (add_1024) {
449
    val = 1024
450
    img = check_nifti(img, allow.array = TRUE)
451
    img = img + val
452
    args$lthresh = args$lthresh + val
453
    args$uthresh = args$uthresh + val
454
    mb = args$mask_to_background
455
    if (!is.null(mb)) {
456
      if (mb) {
457
        warning(paste0("add_1024 is TRUE, but mask_to_background ",
458
                       "TRUE, setting to FALSE"))
459
      }
460
    } else {
461
      mb = FALSE
462
    }
463
    # now 0 is truly 0
464
    args$mask_to_background = FALSE
465
  }
466
  args$img = img
467
468
  retimg = args$retimg
469
  # always return an image
470
  if (is.null(retimg)) {
471
    retimg = TRUE
472
  }
473
  # make sure you return an image and now we know for sure the outfile
474
  if (add_1024) {
475
    args$retimg = TRUE
476
    outfile = args$outfile
477
    if (is.null(outfile)) {
478
      outfile = tempfile(fileext = ".nii.gz")
479
    }
480
    args$outfile = outfile
481
  }
482
  # run the ss
483
  ss = do.call(CT_Skull_Strip_robust, args = args)
484
  # read in the mask
485
  if (add_1024) {
486
    mask = sub("[.]nii", "_Mask.nii", outfile)
487
    mask = readnii(mask)
488
    # remove the 1024
489
    if (mb) {
490
      # background values are -1024
491
      ss = mask_img(img, mask) - val
492
    } else {
493
      # background values are 0
494
      ss = mask_img(img - val, mask)
495
    }
496
    writenii(ss, outfile)
497
  }
498
  if (retimg) {
499
    return(ss)
500
  } else {
501
    return(outfile)
502
  }
503
}