File: tckmap.cpp

package info (click to toggle)
mrtrix3 3.0.8-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,300 kB
  • sloc: cpp: 130,470; python: 9,603; sh: 597; makefile: 62; xml: 47
file content (632 lines) | stat: -rw-r--r-- 25,386 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
/* Copyright (c) 2008-2025 the MRtrix3 contributors.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Covered Software is provided under this License on an "as is"
 * basis, without warranty of any kind, either expressed, implied, or
 * statutory, including, without limitation, warranties that the
 * Covered Software is free of defects, merchantable, fit for a
 * particular purpose or non-infringing.
 * See the Mozilla Public License v. 2.0 for more details.
 *
 * For more details, see http://www.mrtrix.org/.
 */

#include <set>

#include "command.h"
#include "image.h"
#include "memory.h"
#include "progressbar.h"
#include "thread_queue.h"
#include "types.h"

#include "dwi/gradient.h"
#include "dwi/tractography/file.h"
#include "dwi/tractography/properties.h"
#include "dwi/tractography/weights.h"

#include "dwi/tractography/mapping/loader.h"
#include "dwi/tractography/mapping/mapper.h"
#include "dwi/tractography/mapping/mapping.h"
#include "dwi/tractography/mapping/voxel.h"
#include "dwi/tractography/mapping/writer.h"

#include "dwi/tractography/mapping/gaussian/mapper.h"
#include "dwi/tractography/mapping/gaussian/voxel.h"




using namespace MR;
using namespace App;

using namespace MR::DWI;
using namespace MR::DWI::Tractography;
using namespace MR::DWI::Tractography::Mapping;





const OptionGroup OutputHeaderOption = OptionGroup ("Options for the header of the output image")

  + Option ("template",
      "an image file to be used as a template for the output (the output image "
      "will have the same transform and field of view).")
    + Argument ("image").type_image_in()

  + Option ("vox",
      "provide either an isotropic voxel size (in mm), or comma-separated list "
      "of 3 voxel dimensions.")
    + Argument ("size").type_sequence_float()

  + Option ("datatype",
      "specify output image data type.")
    + Argument ("spec").type_choice (DataType::identifiers);





const OptionGroup OutputDimOption = OptionGroup ("Options for the dimensionality of the output image")

    + Option ("dec",
        "perform track mapping in directionally-encoded colour (DEC) space")

    + Option ("dixel",
        "map streamlines to dixels within each voxel; requires either a number of dixels "
        "(references an internal direction set), or a path to a text file containing a "
        "set of directions stored as azimuth/elevation pairs")
      + Argument ("path").type_various()

    + Option ("tod",
        "generate a Track Orientation Distribution (TOD) in each voxel; need to specify the maximum "
        "spherical harmonic degree lmax to use when generating Apodised Point Spread Functions")
      + Argument ("lmax").type_integer (2, 20);





const OptionGroup TWIOption = OptionGroup ("Options for the TWI image contrast properties")

  + Option ("contrast",
      "define the desired form of contrast for the output image\n"
      "Options are: " + join(contrasts, ", ") + " (default: tdi)")
    + Argument ("type").type_choice (contrasts)

  + Option ("image",
      "provide the scalar image map for generating images with 'scalar_map' / 'scalar_map_count' contrast, or the spherical harmonics image for 'fod_amp' contrast")
    + Argument ("image").type_image_in()

  + Option ("vector_file",
      "provide the vector data file for generating images with 'vector_file' contrast")
    + Argument ("path").type_file_in()

  + Option ("stat_vox",
      "define the statistic for choosing the final voxel intensities for a given contrast "
      "type given the individual values from the tracks passing through each voxel. \n"
      "Options are: " + join(voxel_statistics, ", ") + " (default: sum)")
    + Argument ("type").type_choice (voxel_statistics)

  + Option ("stat_tck",
      "define the statistic for choosing the contribution to be made by each streamline as a "
      "function of the samples taken along their lengths. \n"
      "Only has an effect for 'scalar_map', 'fod_amp' and 'curvature' contrast types. \n"
      "Options are: " + join(track_statistics, ", ") + " (default: mean)")
    + Argument ("type").type_choice (track_statistics)

  + Option ("fwhm_tck",
      "when using gaussian-smoothed per-track statistic, specify the "
      "desired full-width half-maximum of the Gaussian smoothing kernel (in mm)")
    + Argument ("value").type_float (1e-6)

  + Option ("map_zero",
      "if a streamline has zero contribution based on the contrast & statistic, typically it is not mapped; "
      "use this option to still contribute to the map even if this is the case "
      "(these non-contributing voxels can then influence the mean value in each voxel of the map)")

  + Option ("backtrack",
      "when using -stat_tck ends_*, if the streamline endpoint is outside the FoV, backtrack along "
      "the streamline trajectory until an appropriate point is found");




const OptionGroup MappingOption = OptionGroup ("Options for the streamline-to-voxel mapping mechanism")

  + Option ("upsample",
      "upsample the tracks by some ratio using Hermite interpolation before mappping\n"
      "(If omitted, an appropriate ratio will be determined automatically)")
    + Argument ("factor").type_integer (1)

  + Option ("precise",
      "use a more precise streamline mapping strategy, that accurately quantifies the length through each voxel "
      "(these lengths are then taken into account during TWI calculation)")

  + Option ("ends_only",
      "only map the streamline endpoints to the image");








void usage () {

AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au) and J-Donald Tournier (jdtournier@gmail.com)";

SYNOPSIS = "Use track data as a form of contrast for producing a high-resolution image";

DESCRIPTION
  + "Note: if you run into limitations with RAM usage, make sure you output the "
    "results to a .mif file or .mih / .dat file pair - this will avoid the allocation "
    "of an additional buffer to store the output for write-out.";

REFERENCES
  + "* For TDI or DEC TDI:\n"
  "Calamante, F.; Tournier, J.-D.; Jackson, G. D. & Connelly, A. " // Internal
  "Track-density imaging (TDI): Super-resolution white matter imaging using whole-brain track-density mapping. "
  "NeuroImage, 2010, 53, 1233-1243"

  + "* If using -contrast length and -stat_vox mean:\n"
  "Pannek, K.; Mathias, J. L.; Bigler, E. D.; Brown, G.; Taylor, J. D. & Rose, S. E. "
  "The average pathlength map: A diffusion MRI tractography-derived index for studying brain pathology. "
  "NeuroImage, 2011, 55, 133-141"

  + "* If using -dixel option with TDI contrast only:\n"
  "Smith, R.E., Tournier, J-D., Calamante, F., Connelly, A. " // Internal
  "A novel paradigm for automated segmentation of very large whole-brain probabilistic tractography data sets. "
  "In proc. ISMRM, 2011, 19, 673"

  + "* If using -dixel option with any other contrast:\n"
  "Pannek, K., Raffelt, D., Salvado, O., Rose, S. " // Internal
  "Incorporating directional information in diffusion tractography derived maps: angular track imaging (ATI). "
  "In Proc. ISMRM, 2012, 20, 1912"

  + "* If using -tod option:\n"
  "Dhollander, T., Emsell, L., Van Hecke, W., Maes, F., Sunaert, S., Suetens, P. " // Internal
  "Track Orientation Density Imaging (TODI) and Track Orientation Distribution (TOD) based tractography. "
  "NeuroImage, 2014, 94, 312-336"

  + "* If using other contrasts / statistics:\n"
  "Calamante, F.; Tournier, J.-D.; Smith, R. E. & Connelly, A. " // Internal
  "A generalised framework for super-resolution track-weighted imaging. "
  "NeuroImage, 2012, 59, 2494-2503"

  + "* If using -precise mapping option:\n"
  "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal
  "SIFT: Spherical-deconvolution informed filtering of tractograms. "
  "NeuroImage, 2013, 67, 298-312 (Appendix 3)";

ARGUMENTS
  + Argument ("tracks", "the input track file.").type_file_in()
  + Argument ("output", "the output track-weighted image").type_image_out();

OPTIONS
  + OutputHeaderOption
  + OutputDimOption
  + TWIOption
  + MappingOption
  + Tractography::TrackWeightsInOption;

}






MapWriterBase* make_writer (Header& H, const std::string& name, const vox_stat_t stat_vox, const writer_dim dim)
{
  MapWriterBase* writer = nullptr;
  const uint8_t dt = uint8_t(H.datatype()()) & DataType::Type;
  if (dt == DataType::Bit)
    writer = new MapWriter<bool>     (H, name, stat_vox, dim);
  else if (dt == DataType::UInt8)
    writer = new MapWriter<uint8_t>  (H, name, stat_vox, dim);
  else if (dt == DataType::UInt16)
    writer = new MapWriter<uint16_t> (H, name, stat_vox, dim);
  else if (dt == DataType::UInt32 || dt == DataType::UInt64)
    writer = new MapWriter<uint32_t> (H, name, stat_vox, dim);
  else if (dt == DataType::Float32 || dt == DataType::Float64)
    writer = new MapWriter<float>    (H, name, stat_vox, dim);
  else
    throw Exception ("Unsupported data type in image header");
  return writer;
}








DataType determine_datatype (const DataType current_dt, const contrast_t contrast, const DataType default_dt, const bool precise)
{
  if (current_dt == DataType::Undefined) {
    return default_dt;
  } else if ((default_dt.is_floating_point() || precise) && !current_dt.is_floating_point()) {
    WARN ("Cannot use non-floating-point datatype with " + str(Mapping::contrasts[contrast]) + " contrast" + (precise ? " and precise mapping" : "") + "; defaulting to " + str(default_dt.specifier()));
    return default_dt;
  } else {
    return current_dt;
  }
}



void run () {

  Tractography::Properties properties;
  Tractography::Reader<float> file (argument[0], properties);

  const size_t num_tracks = properties["count"].empty() ? 0 : to<size_t> (properties["count"]);

  vector<default_type> voxel_size = get_option_value ("vox", vector<default_type>());

  if (voxel_size.size() == 1) {
    auto v = voxel_size.front();                                                                                          
    voxel_size.assign (3, v);                                                                                             
  } else if (!voxel_size.empty() && voxel_size.size() != 3)
    throw Exception ("voxel size must either be a single isotropic value, or a list of 3 comma-separated voxel dimensions");

  if (!voxel_size.empty())
    INFO ("creating image with voxel dimensions [ " + str(voxel_size[0]) + " " + str(voxel_size[1]) + " " + str(voxel_size[2]) + " ]");

  Header header;
  auto opt = get_options ("template");
  if (opt.size()) {
    auto template_header = Header::open (opt[0][0]);
    header = template_header;
    header.keyval().clear();
    header.keyval()["twi_template"] = str(opt[0][0]);
    if (!voxel_size.empty())
      oversample_header (header, voxel_size);
  }
  else {
    if (voxel_size.empty())
      throw Exception ("please specify a template image and/or the desired voxel size");
    generate_header (header, argument[0], voxel_size);
  }

  if (header.ndim() > 3) {
    header.ndim() = 3;
    header.sanitise();
  }

  add_line (header.keyval()["comments"], "track-weighted image");
  header.keyval()["tck_source"] = std::string (argument[0]);

  opt = get_options ("contrast");
  const contrast_t contrast = opt.size() ? contrast_t(int(opt[0][0])) : TDI;

  opt = get_options ("stat_vox");
  vox_stat_t stat_vox = opt.size() ? vox_stat_t(int(opt[0][0])) : V_SUM;

  opt = get_options ("stat_tck");
  tck_stat_t stat_tck = opt.size() ? tck_stat_t(int(opt[0][0])) : T_MEAN;


  float gaussian_fwhm_tck = 0.0;
  opt = get_options ("fwhm_tck");
  if (opt.size()) {
    if (stat_tck != GAUSSIAN) {
      WARN ("Overriding per-track statistic to Gaussian as a full-width half-maximum has been provided.");
      stat_tck = GAUSSIAN;
    }
    gaussian_fwhm_tck = opt[0][0];
  } else if (stat_tck == GAUSSIAN) {
    throw Exception ("If using Gaussian per-streamline statistic, need to provide a full-width half-maximum for the Gaussian kernel using the -fwhm option");
  }


  bool backtrack = false;
  if (get_options ("backtrack").size()) {
    if (stat_tck == ENDS_CORR || stat_tck == ENDS_MAX || stat_tck == ENDS_MEAN || stat_tck == ENDS_MIN || stat_tck == ENDS_PROD)
      backtrack = true;
    else
      WARN ("-backtrack option ignored; only applicable to endpoint-based track statistics");
  }


  // Determine the dimensionality of the output image
  writer_dim writer_type = GREYSCALE;

  opt = get_options ("dec");
  if (opt.size()) {
    writer_type = DEC;
    header.ndim() = 4;
    header.size (3) = 3;
    header.sanitise();
    Stride::set (header, Stride::contiguous_along_axis (3, header));
  }

  std::unique_ptr<Directions::FastLookupSet> dirs;
  opt = get_options ("dixel");
  if (opt.size()) {
    if (writer_type != GREYSCALE)
      throw Exception ("Options for setting output image dimensionality are mutually exclusive");
    writer_type = DIXEL;
    if (Path::exists (opt[0][0]))
      dirs.reset (new Directions::FastLookupSet (str(opt[0][0])));
    else
      dirs.reset (new Directions::FastLookupSet (to<size_t>(opt[0][0])));
    header.ndim() = 4;
    header.size(3) = dirs->size();
    header.sanitise();
    Stride::set (header, Stride::contiguous_along_axis (3, header));
    // Write directions to image header as diffusion encoding
    Eigen::MatrixXd grad (dirs->size(), 4);
    for (size_t row = 0; row != dirs->size(); ++row) {
      grad (row, 0) = ((*dirs)[row])[0];
      grad (row, 1) = ((*dirs)[row])[1];
      grad (row, 2) = ((*dirs)[row])[2];
      grad (row, 3) = 1.0f;
    }
    set_DW_scheme (header, grad);
  }

  opt = get_options ("tod");
  if (opt.size()) {
    if (writer_type != GREYSCALE)
      throw Exception ("Options for setting output image dimensionality are mutually exclusive");
    writer_type = TOD;
    const size_t lmax = opt[0][0];
    if (lmax % 2)
      throw Exception ("lmax for TODI must be an even number");
    header.ndim() = 4;
    header.size(3) = Math::SH::NforL (lmax);
    header.sanitise();
    Stride::set (header, Stride::contiguous_along_axis (3, header));
  }

  header.keyval()["twi_dimensionality"] = writer_dims[writer_type];


  // Deal with erroneous statistics & provide appropriate messages
  switch (contrast) {

    case TDI:
      if (stat_vox != V_SUM && stat_vox != V_MEAN) {
        WARN ("Cannot use voxel statistic other than 'sum' or 'mean' for TDI generation - ignoring");
        stat_vox = V_SUM;
      }
      if (stat_tck != T_MEAN)
        WARN ("Cannot use track statistic other than default for TDI generation - ignoring");
      stat_tck = T_MEAN;
      break;

    case LENGTH:
      if (stat_tck != T_MEAN)
        WARN ("Cannot use track statistic other than default for length-weighted TDI generation - ignoring");
      stat_tck = T_MEAN;
      break;

    case INVLENGTH:
      if (stat_tck != T_MEAN)
        WARN ("Cannot use track statistic other than default for inverse-length-weighted TDI generation - ignoring");
      stat_tck = T_MEAN;
      break;

    case SCALAR_MAP:
    case SCALAR_MAP_COUNT:
    case FOD_AMP:
    case CURVATURE:
      break;

    case VECTOR_FILE:
      if (stat_tck != T_MEAN)
        WARN ("Cannot use track statistic other than default when providing contrast from an external data file - ignoring");
      stat_tck = T_MEAN;
      break;

    default:
      throw Exception ("Undefined contrast mechanism");

  }


  header.keyval()["twi_contrast"] = contrasts[contrast];
  header.keyval()["twi_vox_stat"] = voxel_statistics[stat_vox];
  header.keyval()["twi_tck_stat"] = track_statistics[stat_tck];
  if (backtrack)
    header.keyval()["twi_backtrack"] = "1";


  // Figure out how the streamlines will be mapped
  const bool precise = get_options ("precise").size();
  header.keyval()["precise_mapping"] = precise ? "1" : "0";
  const bool ends_only = get_options ("ends_only").size();
  if (ends_only) {
    if (precise)
      throw Exception ("Options -precise and -ends_only are mutually exclusive");
    header.keyval()["endpoints_only"] = "1";
  }

  size_t upsample_ratio = 1;
  opt = get_options ("upsample");
  if (opt.size()) {
    if (ends_only) {
      WARN ("cannot use upsampling if only streamline endpoints are to be mapped");
    } else {
      upsample_ratio = opt[0][0];
      INFO ("track upsampling ratio manually set to " + str(upsample_ratio));
    }
  } else if (!ends_only) {
    // If accurately calculating the length through each voxel traversed, need a higher upsampling ratio
    //   (1/10th of the voxel size was found to give a good quantification of chordal length)
    // For all other applications, making the upsampled step size about 1/3rd of a voxel seems sufficient
    try {
      upsample_ratio = determine_upsample_ratio (header, properties, (precise ? 0.1 : 0.333));
      INFO ("track upsampling ratio automatically set to " + str(upsample_ratio));
    } catch (Exception& e) {
      e.push_back ("Try using -upsample option to explicitly set the streamline upsampling ratio;");
      e.push_back ("generally recommend a value of around (3 x step_size / voxel_size)");
      throw e;
    }
  }


  // Get header datatype based on user input, or select an appropriate datatype automatically
  header.datatype() = DataType::Undefined;
  if (writer_type == DEC)
    header.datatype() = DataType::Float32;

  opt = get_options ("datatype");
  if (opt.size()) {
    if (writer_type == DEC || writer_type == TOD) {
      WARN ("Can't manually set datatype for " + str(Mapping::writer_dims[writer_type]) + " processing - overriding to Float32");
    } else {
      header.datatype() = DataType::parse (opt[0][0]);
    }
  }

  const bool have_weights = get_options ("tck_weights_in").size();
  if (have_weights && header.datatype().is_integer()) {
    WARN ("Can't use an integer type if streamline weights are provided; overriding to Float32");
    header.datatype() = DataType::Float32;
  }

  DataType default_datatype = DataType::Float32;
  if ((writer_type == GREYSCALE || writer_type == DIXEL) && !have_weights && ((!precise && contrast == TDI) || contrast == SCALAR_MAP_COUNT))
    default_datatype = DataType::UInt32;
  header.datatype() = determine_datatype (header.datatype(), contrast, default_datatype, precise);
  header.datatype().set_byte_order_native();


  // Whether or not to still ,ap streamlines even if the factor is zero
  //   (can still affect output image if voxel-wise statistic is mean)
  const bool map_zero = get_options ("map_zero").size();
  if (map_zero)
    header.keyval()["map_zero"] = "1";



  // Produce a useful INFO message
  std::string msg = std::string("Generating ") + Mapping::writer_dims[writer_type] + " image with ";
  switch (contrast) {
    case TDI:              msg += "density";                    break;
    case LENGTH:           msg += "length";                     break;
    case INVLENGTH:        msg += "inverse length";             break;
    case SCALAR_MAP:       msg += "scalar map";                 break;
    case SCALAR_MAP_COUNT: msg += "scalar-map-thresholded tdi"; break;
    case FOD_AMP:          msg += "FOD amplitude";              break;
    case CURVATURE:        msg += "curvature";                  break;
    case VECTOR_FILE:      msg += "external-file-based";        break;
    default:               msg += "ERROR";                      break;
  }
  msg += " contrast";
  if (contrast == SCALAR_MAP || contrast == SCALAR_MAP_COUNT || contrast == FOD_AMP || contrast == CURVATURE)
    msg += ", ";
  else
    msg += " and ";
  switch (stat_vox) {
    case V_SUM:  msg += "summed";  break;
    case V_MIN:  msg += "minimum"; break;
    case V_MEAN: msg += "mean";    break;
    case V_MAX:  msg += "maximum"; break;
    default:     msg += "ERROR";   break;
  }
  msg += " per-voxel statistic";
  if (contrast == SCALAR_MAP || contrast == SCALAR_MAP_COUNT || contrast == FOD_AMP || contrast == CURVATURE) {
    msg += " and ";
    switch (stat_tck) {
      case T_SUM:          msg += "summed";  break;
      case T_MIN:          msg += "minimum"; break;
      case T_MEAN:         msg += "mean";    break;
      case T_MAX:          msg += "maximum"; break;
      case T_MEDIAN:       msg += "median";  break;
      case T_MEAN_NONZERO: msg += "mean (nonzero)"; break;
      case GAUSSIAN:       msg += "gaussian (FWHM " + str (gaussian_fwhm_tck) + "mm)"; break;
      case ENDS_MIN:       msg += "endpoints (minimum)"; break;
      case ENDS_MEAN:      msg += "endpoints (mean)"; break;
      case ENDS_MAX:       msg += "endpoints (maximum)"; break;
      case ENDS_PROD:      msg += "endpoints (product)"; break;
      default:             throw Exception ("Invalid track-wise statistic detected");
    }
    msg += " per-track statistic";
  }
  INFO (msg);


  // Start initialising members for multi-threaded calculation
  TrackLoader loader (file, num_tracks);

  std::unique_ptr<TrackMapperTWI> mapper ((stat_tck == GAUSSIAN) ? (new Gaussian::TrackMapper (header, contrast)) : (new TrackMapperTWI (header, contrast, stat_tck)));
  mapper->set_upsample_ratio      (upsample_ratio);
  mapper->set_map_zero            (map_zero);
  mapper->set_use_precise_mapping (precise);
  mapper->set_map_ends_only       (ends_only);
  if (writer_type == DIXEL)
    mapper->create_dixel_plugin (*dirs);
  if (writer_type == TOD)
    mapper->create_tod_plugin (header.size(3));
  if (contrast == SCALAR_MAP || contrast == SCALAR_MAP_COUNT || contrast == FOD_AMP) {
    opt = get_options ("image");
    if (!opt.size()) {
      if (contrast == SCALAR_MAP || contrast == SCALAR_MAP_COUNT)
        throw Exception ("If using 'scalar_map' or 'scalar_map_count' contrast, must provide the relevant scalar image using -image option");
      else
        throw Exception ("If using 'fod_amp' contrast, must provide the relevant spherical harmonic image using -image option");
    }
    const std::string assoc_image (opt[0][0]);
    if (contrast == SCALAR_MAP || contrast == SCALAR_MAP_COUNT) {
      mapper->add_scalar_image (assoc_image);
      if (backtrack)
        mapper->set_backtrack();
    } else {
      mapper->add_fod_image (assoc_image);
    }
    header.keyval()["twi_assoc_image"] = Path::basename (assoc_image);
  } else if (contrast == VECTOR_FILE) {
    opt = get_options ("vector_file");
    if (!opt.size())
      throw Exception ("If using 'vector_file' contrast, must provide the relevant data file using the -vector_file option");
    const std::string path (opt[0][0]);
    mapper->add_vector_data (path);
    header.keyval()["twi_vector_file"] = Path::basename (path);
  }

  std::unique_ptr<MapWriterBase> writer;
  switch (writer_type) {
    case UNDEFINED: throw Exception ("Invalid TWI writer image dimensionality");
    case GREYSCALE: writer.reset (make_writer           (header, argument[1], stat_vox, GREYSCALE)); break;
    case DEC:       writer.reset (new MapWriter<float>  (header, argument[1], stat_vox, DEC));       break;
    case DIXEL:     writer.reset (make_writer           (header, argument[1], stat_vox, DIXEL));     break;
    case TOD:       writer.reset (new MapWriter<float>  (header, argument[1], stat_vox, TOD));       break;
  }

  // Finally get to do some number crunching!
  // Complete branch here for Gaussian track-wise statistic; it's a nightmare to manage, so am
  //   keeping the code as separate as possible
  if (stat_tck == GAUSSIAN) {
    Gaussian::TrackMapper* const mapper_ptr = dynamic_cast<Gaussian::TrackMapper*>(mapper.get());
    mapper_ptr->set_gaussian_FWHM (gaussian_fwhm_tck);
    switch (writer_type) {
      case UNDEFINED: throw Exception ("Invalid TWI writer image dimensionality");
      case GREYSCALE: Thread::run_queue (loader, Thread::batch (Tractography::Streamline<float>()), Thread::multi (*mapper_ptr), Thread::batch (Gaussian::SetVoxel()),    *writer); break;
      case DEC:       Thread::run_queue (loader, Thread::batch (Tractography::Streamline<float>()), Thread::multi (*mapper_ptr), Thread::batch (Gaussian::SetVoxelDEC()), *writer); break;
      case DIXEL:     Thread::run_queue (loader, Thread::batch (Tractography::Streamline<float>()), Thread::multi (*mapper_ptr), Thread::batch (Gaussian::SetDixel()),    *writer); break;
      case TOD:       Thread::run_queue (loader, Thread::batch (Tractography::Streamline<float>()), Thread::multi (*mapper_ptr), Thread::batch (Gaussian::SetVoxelTOD()), *writer); break;
    }
  } else {
    switch (writer_type) {
      case UNDEFINED: throw Exception ("Invalid TWI writer image dimensionality");
      case GREYSCALE: Thread::run_queue (loader, Thread::batch (Tractography::Streamline<float>()), Thread::multi (*mapper), Thread::batch (SetVoxel()),    *writer); break;
      case DEC:       Thread::run_queue (loader, Thread::batch (Tractography::Streamline<float>()), Thread::multi (*mapper), Thread::batch (SetVoxelDEC()), *writer); break;
      case DIXEL:     Thread::run_queue (loader, Thread::batch (Tractography::Streamline<float>()), Thread::multi (*mapper), Thread::batch (SetDixel()),    *writer); break;
      case TOD:       Thread::run_queue (loader, Thread::batch (Tractography::Streamline<float>()), Thread::multi (*mapper), Thread::batch (SetVoxelTOD()), *writer); break;
    }
  }

  writer->finalise();
}