diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..5a67a0d33f --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +Language: Cpp +BasedOnStyle: LLVM +BinPackArguments: false +BinPackParameters: false +PackConstructorInitializers: NextLine +ColumnLimit: 120 \ No newline at end of file diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 9c547f300a..1279f0b2e5 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -97,3 +97,8 @@ aad44d847ac48d02bb7f8badf801dbfaa0ccdac0 #Author: MRtrixBot #Date: Tue Jan 3 13:42:58 2023 +0100 # Update Copyright notice in command docs + +45fc06bdf687584bf4d55140720e84c5ef517bf0 +#Author: MRtrixBot +#Date: Thu, 17 Aug 2023 15:36:14 +0100 +# Apply formatting for C++ using clang-format \ No newline at end of file diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index e552cb11f1..31f7946f22 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -214,3 +214,16 @@ jobs: - name: check building of documentation run: python3 -m sphinx -n -N -W -w sphinx.log docs/ tmp/ + + clang-format-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + - name: clang-format + uses: jidicula/clang-format-action@v4.11.0 + with: + clang-format-version: '16' + fallback-style: 'LLVM' + # Ignore third-party headers. + exclude-regex: 'core/file/(json|nifti[12]).h' \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..30a546e831 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +default_language_version: + python: python3 +repos: + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v16.0.4 + hooks: + - id: clang-format + name: clang-format + types_or: [c++] + language: python diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa2ead2e6e..cdcff3cf94 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -307,6 +307,33 @@ A few explicit notes on such: introduce Windows-style newline characters ("`CR LF`" / "`\r\n`") will need to be edited accordingly. +- In C++, we use [clang-format](https://clang.llvm.org/docs/ClangFormat.html) 16 + to ensure that all C++ code is formatted using the same conventions. + To comply with this requirement, we recommend that you install a local git hook + using [pre-commit](https://pre-commit.com/): + + - Install `pre-commit` via `pip install pre-commit` (or via your OS package manager). + + - Run `pre-commit install` in the source directory of MRtrix. + NOTE: you may need to add the pip scripts folder to your `PATH` + (you can check where pip has installed the package using `pip show pre-commit`). + + This procedure will install a local git hook to ensure that all of your commits + are correctly formatted. If this isn't the case, the commits will fail and an + automatic formatting of the staged changes will be applied. You will then have + to commit those changes again. + + Alternatively, you may wish to configure your editor to automatically format + your code using `clang-format`. We also provide a Python script in the top level directory + called `clang-format-all` that can format all C++ code in the repository. You can run + this to format your code before committing it. The script also allows you to + specify a custom `clang-format` binary by using the `--executable` flag. + + NOTE: If you are using a version of `clang-format` that is different from the one + used by the CI (currently version 16), your code may be formatted differently and thus + fail our CI tests. We recommend that you use the same version of `clang-format` as the + CI to avoid this issue. + - In Python, variable / class / module names are enforced through `pylint`. Script "`run_pylint`" in the *MRtrix3* root directory will test any code modifications against these expectations. diff --git a/clang-format-all b/clang-format-all new file mode 100644 index 0000000000..2d6231b278 --- /dev/null +++ b/clang-format-all @@ -0,0 +1,85 @@ +#!/usr/env/python3 + +# Copyright (c) 2008-2023 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/. + +import os +import argparse +import subprocess +import time +from pathlib import Path +from multiprocessing import cpu_count + +parser = argparse.ArgumentParser( + description='Run clang-format on C++ source files.' +) + +parser.add_argument( + '-e', '--executable', + help='Path to the clang-format executable.', + default='clang-format' +) + +args = parser.parse_args() +clang_format = args.executable +paths = ['core', 'cmd', 'src', 'testing'] +extensions = ['.h', '.cpp'] +exclusion_list = ['core/file/nifti1.h', + 'core/file/nifti2.h', + 'core/file/json.h'] + +# if clang-format path contains spaces, wrap it in quotes +if ' ' in clang_format and not clang_format.startswith('"'): + clang_format = '"{}"'.format(clang_format) + +if os.system('{} -version'.format(clang_format)) != 0: + raise RuntimeError('Could not find clang-format executable.') + + +def skip_file(file): + """Return True if the file should be skipped.""" + return file.suffix not in extensions or file.as_posix() in exclusion_list + + +def format_file(file): + """Run clang-format on a file.""" + command = '{} -i {}'.format(clang_format, file) + return subprocess.Popen(command, shell=True) + + +files = [] +for path in paths: + files += [file for file in list(Path(path).rglob('*.*')) + if not skip_file(file)] + +print('Found {} files to format.'.format(len(files))) + +# We want a maximum of num_cpus processes running at once +processes = [] +num_cpus = cpu_count() +total = len(files) + +try: + while len(files) > 0 or len(processes) > 0: + processes = [proc for proc in processes if proc.poll() is None] + schedule_count = min(num_cpus - len(processes), len(files)) + processes += [format_file(files.pop(0)) for _ in range(schedule_count)] + print('Formatted {}/{} files.'.format(total - len(files), total), end='\r') + time.sleep(0.01) + +except KeyboardInterrupt: + print('Keyboard interrupt received, terminating formatting.') + for process in processes: + process.terminate() diff --git a/cmd/5tt2gmwmi.cpp b/cmd/5tt2gmwmi.cpp index b11fd099e8..b486ec385d 100644 --- a/cmd/5tt2gmwmi.cpp +++ b/cmd/5tt2gmwmi.cpp @@ -25,110 +25,100 @@ #include "dwi/tractography/ACT/act.h" #include "dwi/tractography/ACT/tissues.h" - - using namespace MR; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Generate a mask image appropriate for seeding streamlines on the grey matter-white matter interface"; REFERENCES - + "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal - "Anatomically-constrained tractography:" - "Improved diffusion MRI streamlines tractography through effective use of anatomical information. " - "NeuroImage, 2012, 62, 1924-1938"; + +"Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal + "Anatomically-constrained tractography:" + "Improved diffusion MRI streamlines tractography through effective use of anatomical information. " + "NeuroImage, 2012, 62, 1924-1938"; ARGUMENTS - + Argument ("5tt_in", "the input 5TT segmented anatomical image").type_image_in() - + Argument ("mask_out", "the output mask image") .type_image_out(); + +Argument("5tt_in", "the input 5TT segmented anatomical image").type_image_in() + + Argument("mask_out", "the output mask image").type_image_out(); OPTIONS - + Option("mask_in", "Filter an input mask image according to those voxels that lie upon the grey matter - white matter boundary. " - "If no input mask is provided, the output will be a whole-brain mask image calculated using the anatomical image only.") - + Argument ("image", "the input mask image").type_image_in(); - + +Option("mask_in", + "Filter an input mask image according to those voxels that lie upon the grey matter - white matter boundary. " + "If no input mask is provided, the output will be a whole-brain mask image calculated using the anatomical " + "image only.") + + Argument("image", "the input mask image").type_image_in(); } +class Processor { +public: + Processor(const Image &mask) : mask(mask) {} + Processor(const Processor &) = default; -class Processor -{ - - public: - Processor (const Image& mask) : mask (mask) { } - Processor (const Processor&) = default; - - bool operator() (Image& input, Image& output) - { - // If a mask is defined, but is false in this voxel, do not continue processing - bool process_voxel = true; - if (mask.valid()) { - assign_pos_of (input, 0, 3).to (mask); - process_voxel = mask.value(); - } - if (process_voxel) { - // Generate a non-binary seeding mask. - // Image intensity should be proportional to the tissue gradient present across the voxel - // Remember: This seeding mask is generated in the same space as the 5TT image: exploit this - // (no interpolators should be necessary) - // Essentially looking for an absolute gradient in (GM - WM) - just do in three axes - // - well, not quite; needs to be the minimum of the two - default_type gradient = 0.0; - for (size_t axis = 0; axis != 3; ++axis) { - assign_pos_of (output, 0, 3).to (input); - default_type multiplier = 0.5; - if (!output.index(axis)) { - multiplier = 1.0; - } else { - input.move_index (axis, -1); - } - const DWI::Tractography::ACT::Tissues neg (input); - if (output.index(axis) == output.size(axis)-1) { - multiplier = 1.0; - input.index(axis) = output.index(axis); - } else { - input.index(axis) = output.index(axis) + 1; - } - const DWI::Tractography::ACT::Tissues pos (input); - gradient += Math::pow2 (multiplier * std::min (abs (pos.get_gm() - neg.get_gm()), abs (pos.get_wm() - neg.get_wm()))); + bool operator()(Image &input, Image &output) { + // If a mask is defined, but is false in this voxel, do not continue processing + bool process_voxel = true; + if (mask.valid()) { + assign_pos_of(input, 0, 3).to(mask); + process_voxel = mask.value(); + } + if (process_voxel) { + // Generate a non-binary seeding mask. + // Image intensity should be proportional to the tissue gradient present across the voxel + // Remember: This seeding mask is generated in the same space as the 5TT image: exploit this + // (no interpolators should be necessary) + // Essentially looking for an absolute gradient in (GM - WM) - just do in three axes + // - well, not quite; needs to be the minimum of the two + default_type gradient = 0.0; + for (size_t axis = 0; axis != 3; ++axis) { + assign_pos_of(output, 0, 3).to(input); + default_type multiplier = 0.5; + if (!output.index(axis)) { + multiplier = 1.0; + } else { + input.move_index(axis, -1); } - output.value() = std::max (0.0, std::sqrt (gradient)); - assign_pos_of (output, 0, 3).to (input); - } else { - output.value() = 0.0f; + const DWI::Tractography::ACT::Tissues neg(input); + if (output.index(axis) == output.size(axis) - 1) { + multiplier = 1.0; + input.index(axis) = output.index(axis); + } else { + input.index(axis) = output.index(axis) + 1; + } + const DWI::Tractography::ACT::Tissues pos(input); + gradient += + Math::pow2(multiplier * std::min(abs(pos.get_gm() - neg.get_gm()), abs(pos.get_wm() - neg.get_wm()))); } - return true; + output.value() = std::max(0.0, std::sqrt(gradient)); + assign_pos_of(output, 0, 3).to(input); + } else { + output.value() = 0.0f; } + return true; + } - private: - Image mask; - +private: + Image mask; }; +void run() { - -void run () -{ - - auto input = Image::open (argument[0]); - DWI::Tractography::ACT::verify_5TT_image (input); - check_3D_nonunity (input); + auto input = Image::open(argument[0]); + DWI::Tractography::ACT::verify_5TT_image(input); + check_3D_nonunity(input); // TODO It would be nice to have the capability to define this mask based on another image // This will however require the use of interpolators Image mask; - auto opt = get_options ("mask_in"); + auto opt = get_options("mask_in"); if (opt.size()) { - mask = Image::open (opt[0][0]); - if (!dimensions_match (input, mask, 0, 3)) - throw Exception ("Mask image provided using the -mask option must match the input 5TT image"); + mask = Image::open(opt[0][0]); + if (!dimensions_match(input, mask, 0, 3)) + throw Exception("Mask image provided using the -mask option must match the input 5TT image"); } Header H; @@ -140,10 +130,7 @@ void run () H = input; H.ndim() = 3; } - auto output = Image::create (argument[1], H); - - ThreadedLoop ("Generating GMWMI seed mask", input, 0, 3) - .run (Processor (mask), input, output); + auto output = Image::create(argument[1], H); + ThreadedLoop("Generating GMWMI seed mask", input, 0, 3).run(Processor(mask), input, output); } - diff --git a/cmd/5tt2vis.cpp b/cmd/5tt2vis.cpp index c087467edf..0e43c5c4e4 100644 --- a/cmd/5tt2vis.cpp +++ b/cmd/5tt2vis.cpp @@ -22,85 +22,69 @@ #include "dwi/tractography/ACT/act.h" #include "dwi/tractography/ACT/tissues.h" - using namespace MR; using namespace App; - - -#define VALUE_DEFAULT_BG 0.0 -#define VALUE_DEFAULT_CGM 0.5 -#define VALUE_DEFAULT_SGM 0.75 -#define VALUE_DEFAULT_WM 1.0 -#define VALUE_DEFAULT_CSF 0.15 +#define VALUE_DEFAULT_BG 0.0 +#define VALUE_DEFAULT_CGM 0.5 +#define VALUE_DEFAULT_SGM 0.75 +#define VALUE_DEFAULT_WM 1.0 +#define VALUE_DEFAULT_CSF 0.15 #define VALUE_DEFAULT_PATH 2.0 - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Generate an image for visualisation purposes from an ACT 5TT segmented anatomical image"; ARGUMENTS - + Argument ("input", "the input 4D tissue-segmented image").type_image_in() - + Argument ("output", "the output 3D image for visualisation").type_image_out(); + +Argument("input", "the input 4D tissue-segmented image").type_image_in() + + Argument("output", "the output 3D image for visualisation").type_image_out(); OPTIONS - + Option ("bg", "image intensity of background (default: " + str(VALUE_DEFAULT_BG, 2) + ")") - + Argument ("value").type_float (0.0, 1.0) - - + Option ("cgm", "image intensity of cortical grey matter (default: " + str(VALUE_DEFAULT_CGM, 2) + ")") - + Argument ("value").type_float (0.0, 1.0) + +Option("bg", "image intensity of background (default: " + str(VALUE_DEFAULT_BG, 2) + ")") + + Argument("value").type_float(0.0, 1.0) - + Option ("sgm", "image intensity of sub-cortical grey matter (default: " + str(VALUE_DEFAULT_SGM, 2) + ")") - + Argument ("value").type_float (0.0, 1.0) + + Option("cgm", "image intensity of cortical grey matter (default: " + str(VALUE_DEFAULT_CGM, 2) + ")") + + Argument("value").type_float(0.0, 1.0) - + Option ("wm", "image intensity of white matter (default: " + str(VALUE_DEFAULT_WM, 2) + ")") - + Argument ("value").type_float (0.0, 1.0) + + Option("sgm", "image intensity of sub-cortical grey matter (default: " + str(VALUE_DEFAULT_SGM, 2) + ")") + + Argument("value").type_float(0.0, 1.0) - + Option ("csf", "image intensity of CSF (default: " + str(VALUE_DEFAULT_CSF, 2) + ")") - + Argument ("value").type_float (0.0, 1.0) + + Option("wm", "image intensity of white matter (default: " + str(VALUE_DEFAULT_WM, 2) + ")") + + Argument("value").type_float(0.0, 1.0) - + Option ("path", "image intensity of pathological tissue (default: " + str(VALUE_DEFAULT_PATH, 2) + ")") - + Argument ("value").type_float (0.0, 10.0); + + Option("csf", "image intensity of CSF (default: " + str(VALUE_DEFAULT_CSF, 2) + ")") + + Argument("value").type_float(0.0, 1.0) + + Option("path", "image intensity of pathological tissue (default: " + str(VALUE_DEFAULT_PATH, 2) + ")") + + Argument("value").type_float(0.0, 10.0); } +void run() { + auto input = Image::open(argument[0]); + DWI::Tractography::ACT::verify_5TT_image(input); - - - - -void run () -{ - - auto input = Image::open (argument[0]); - DWI::Tractography::ACT::verify_5TT_image (input); - - Header H (input); + Header H(input); H.ndim() = 3; - const float bg_multiplier = get_option_value ("bg", VALUE_DEFAULT_BG); - const float cgm_multiplier = get_option_value ("cgm", VALUE_DEFAULT_CGM); - const float sgm_multiplier = get_option_value ("sgm", VALUE_DEFAULT_SGM); - const float wm_multiplier = get_option_value ("wm", VALUE_DEFAULT_WM); - const float csf_multiplier = get_option_value ("csf", VALUE_DEFAULT_CSF); - const float path_multiplier = get_option_value ("path", VALUE_DEFAULT_PATH); + const float bg_multiplier = get_option_value("bg", VALUE_DEFAULT_BG); + const float cgm_multiplier = get_option_value("cgm", VALUE_DEFAULT_CGM); + const float sgm_multiplier = get_option_value("sgm", VALUE_DEFAULT_SGM); + const float wm_multiplier = get_option_value("wm", VALUE_DEFAULT_WM); + const float csf_multiplier = get_option_value("csf", VALUE_DEFAULT_CSF); + const float path_multiplier = get_option_value("path", VALUE_DEFAULT_PATH); - auto output = Image::create (argument[1], H); + auto output = Image::create(argument[1], H); - auto f = [&] (decltype(input)& in, decltype(output)& out) { - const DWI::Tractography::ACT::Tissues t (in); + auto f = [&](decltype(input) &in, decltype(output) &out) { + const DWI::Tractography::ACT::Tissues t(in); const float bg = 1.0 - (t.get_cgm() + t.get_sgm() + t.get_wm() + t.get_csf() + t.get_path()); - out.value() = (bg_multiplier * bg) + (cgm_multiplier * t.get_cgm()) + (sgm_multiplier * t.get_sgm()) - + (wm_multiplier * t.get_wm()) + (csf_multiplier * t.get_csf()) + (path_multiplier * t.get_path()); + out.value() = (bg_multiplier * bg) + (cgm_multiplier * t.get_cgm()) + (sgm_multiplier * t.get_sgm()) + + (wm_multiplier * t.get_wm()) + (csf_multiplier * t.get_csf()) + (path_multiplier * t.get_path()); }; - ThreadedLoop (output).run (f, input, output); - + ThreadedLoop(output).run(f, input, output); } - diff --git a/cmd/5ttcheck.cpp b/cmd/5ttcheck.cpp index 11512c8661..ae7d7c44a6 100644 --- a/cmd/5ttcheck.cpp +++ b/cmd/5ttcheck.cpp @@ -26,47 +26,38 @@ #include "dwi/tractography/ACT/act.h" - using namespace MR; using namespace App; - - #define MAX_ERROR 0.001 - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Thoroughly check that one or more images conform to the expected ACT five-tissue-type (5TT) format"; ARGUMENTS - + Argument ("input", "the 5TT image(s) to be tested").type_image_in().allow_multiple(); + +Argument("input", "the 5TT image(s) to be tested").type_image_in().allow_multiple(); OPTIONS - + Option ("voxels", "output mask images highlighting voxels where the input does not conform to 5TT requirements") - + Argument ("prefix").type_text(); + +Option("voxels", "output mask images highlighting voxels where the input does not conform to 5TT requirements") + + Argument("prefix").type_text(); } - - -void run () -{ - const std::string voxels_prefix = get_option_value ("voxels", ""); +void run() { + const std::string voxels_prefix = get_option_value("voxels", ""); size_t major_error_count = 0, minor_error_count = 0; for (size_t i = 0; i != argument.size(); ++i) { - auto in = Image::open (argument[i]); + auto in = Image::open(argument[i]); Image voxels; - Header H_out (in); + Header H_out(in); H_out.ndim() = 3; H_out.datatype() = DataType::Bit; if (voxels_prefix.size()) - voxels = Image::scratch (H_out, "Scratch image for " + argument[i]); + voxels = Image::scratch(H_out, "Scratch image for " + argument[i]); try { @@ -74,56 +65,59 @@ void run () // - Floating-point image // - 4-dimensional // - 5 volumes - DWI::Tractography::ACT::verify_5TT_image (in); + DWI::Tractography::ACT::verify_5TT_image(in); // Manually loop through all voxels, make sure that for every voxel the // sum of tissue partial volumes is either 0 or 1 (or close enough) // Also make sure that individual partial volume fractions do not lie // outside the range [0.0, 1.0] size_t voxel_error_sum = 0, voxel_error_abs = 0; - for (auto outer = Loop(in, 0, 3) (in); outer; ++outer) { + for (auto outer = Loop(in, 0, 3)(in); outer; ++outer) { default_type sum = 0.0; bool abs_error = false; - for (auto inner = Loop(3) (in); inner; ++inner) { + for (auto inner = Loop(3)(in); inner; ++inner) { sum += in.value(); if (in.value() < 0.0f || in.value() > 1.0f) abs_error = true; } - if (!sum) continue; - if (abs (sum-1.0) > MAX_ERROR) { + if (!sum) + continue; + if (abs(sum - 1.0) > MAX_ERROR) { ++voxel_error_sum; if (voxels.valid()) { - assign_pos_of (in, 0, 3).to (voxels); + assign_pos_of(in, 0, 3).to(voxels); voxels.value() = true; } } if (abs_error) { ++voxel_error_abs; if (voxels.valid()) { - assign_pos_of (in, 0, 3).to (voxels); + assign_pos_of(in, 0, 3).to(voxels); voxels.value() = true; } } } if (voxel_error_sum == 1) { - INFO ("Image \"" + argument[i] + "\" contains just one isolated voxel with non-unity sum of partial volume fractions"); + INFO("Image \"" + argument[i] + + "\" contains just one isolated voxel with non-unity sum of partial volume fractions"); } else if (voxel_error_sum) { - WARN ("Image \"" + argument[i] + "\" contains " + str(voxel_error_sum) + " brain voxels with non-unity sum of partial volume fractions"); + WARN("Image \"" + argument[i] + "\" contains " + str(voxel_error_sum) + + " brain voxels with non-unity sum of partial volume fractions"); if (!voxel_error_abs) ++minor_error_count; } else if (!voxel_error_abs) { - INFO ("Image \"" + argument[i] + "\" conforms to 5TT format"); + INFO("Image \"" + argument[i] + "\" conforms to 5TT format"); } if ((voxel_error_sum || voxel_error_abs) && voxels.valid()) { std::string path = voxels_prefix; if (argument.size() > 1) { - path += Path::basename (argument[i]); + path += Path::basename(argument[i]); } else { bool has_extension = false; for (auto p = MR::Formats::known_extensions; *p; ++p) { - if (Path::has_suffix (path, std::string (*p))) { + if (Path::has_suffix(path, std::string(*p))) { has_extension = true; break; } @@ -131,35 +125,41 @@ void run () if (!has_extension) path += ".mif"; } - auto out = Image::create (path, H_out); - copy (voxels, out); + auto out = Image::create(path, H_out); + copy(voxels, out); } if (voxel_error_abs) - throw Exception ("Image \"" + argument[i] + "\" contains " + str(voxel_error_abs) + " brain voxels with a non-physical partial volume fraction"); + throw Exception("Image \"" + argument[i] + "\" contains " + str(voxel_error_abs) + + " brain voxels with a non-physical partial volume fraction"); - } catch (Exception& e) { + } catch (Exception &e) { e.display(); - WARN ("Image \"" + argument[i] + "\" does not conform to fundamental 5TT format requirements"); + WARN("Image \"" + argument[i] + "\" does not conform to fundamental 5TT format requirements"); ++major_error_count; } } - const std::string vox_option_suggestion = get_options ("masks").size() ? (" (suggest checking " + std::string(argument.size() > 1 ? "outputs from" : "output of") + " -masks option)") : " (suggest re-running using the -masks option to see voxels where tissue fractions do not sum to 1.0)"; + const std::string vox_option_suggestion = + get_options("masks").size() + ? (" (suggest checking " + std::string(argument.size() > 1 ? "outputs from" : "output of") + + " -masks option)") + : " (suggest re-running using the -masks option to see voxels where tissue fractions do not sum to 1.0)"; if (major_error_count) { if (argument.size() > 1) - throw Exception (str(major_error_count) + " input image" + (major_error_count > 1 ? "s do" : " does") + " not conform to 5TT format"); + throw Exception(str(major_error_count) + " input image" + (major_error_count > 1 ? "s do" : " does") + + " not conform to 5TT format"); else - throw Exception ("Input image does not conform to 5TT format"); + throw Exception("Input image does not conform to 5TT format"); } else if (minor_error_count) { if (argument.size() > 1) { - WARN (str(minor_error_count) + " input image" + (minor_error_count > 1 ? "s do" : " does") + " not perfectly conform to 5TT format, but may still be applicable" + vox_option_suggestion); + WARN(str(minor_error_count) + " input image" + (minor_error_count > 1 ? "s do" : " does") + + " not perfectly conform to 5TT format, but may still be applicable" + vox_option_suggestion); } else { - WARN ("Input image does not perfectly conform to 5TT format, but may still be applicable" + vox_option_suggestion); + WARN("Input image does not perfectly conform to 5TT format, but may still be applicable" + vox_option_suggestion); } } else { CONSOLE(std::string(argument.size() > 1 ? "All images" : "Input image") + " checked OK"); } } - diff --git a/cmd/5ttedit.cpp b/cmd/5ttedit.cpp index d58b3c11fd..0b757fd308 100644 --- a/cmd/5ttedit.cpp +++ b/cmd/5ttedit.cpp @@ -24,139 +24,128 @@ #include "dwi/tractography/ACT/act.h" - using namespace MR; using namespace App; - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Manually set the partial volume fractions in an ACT five-tissue-type (5TT) image using mask images"; ARGUMENTS - + Argument ("input", "the 5TT image to be modified").type_image_in() - + Argument ("output", "the output modified 5TT image").type_image_out(); + +Argument("input", "the 5TT image to be modified").type_image_in() + + Argument("output", "the output modified 5TT image").type_image_out(); OPTIONS - + Option ("cgm", "provide a mask of voxels that should be set to cortical grey matter") - + Argument ("image").type_image_in() - - + Option ("sgm", "provide a mask of voxels that should be set to sub-cortical grey matter") - + Argument ("image").type_image_in() + +Option("cgm", "provide a mask of voxels that should be set to cortical grey matter") + + Argument("image").type_image_in() - + Option ("wm", "provide a mask of voxels that should be set to white matter") - + Argument ("image").type_image_in() + + Option("sgm", "provide a mask of voxels that should be set to sub-cortical grey matter") + + Argument("image").type_image_in() - + Option ("csf", "provide a mask of voxels that should be set to CSF") - + Argument ("image").type_image_in() + + Option("wm", "provide a mask of voxels that should be set to white matter") + Argument("image").type_image_in() - + Option ("path", "provide a mask of voxels that should be set to pathological tissue") - + Argument ("image").type_image_in() + + Option("csf", "provide a mask of voxels that should be set to CSF") + Argument("image").type_image_in() - + Option ("none", "provide a mask of voxels that should be cleared (i.e. are non-brain); note that this will supersede all other provided masks") - + Argument ("image").type_image_in(); + + Option("path", "provide a mask of voxels that should be set to pathological tissue") + + Argument("image").type_image_in() + + Option("none", + "provide a mask of voxels that should be cleared (i.e. are non-brain); note that this will supersede " + "all other provided masks") + + Argument("image").type_image_in(); } - - - -class Modifier -{ - public: - Modifier (Image& input_image, Image& output_image) : - v_in (input_image), - v_out (output_image) { } - - void set_cgm_mask (const std::string& path) { load (path, 0); } - void set_sgm_mask (const std::string& path) { load (path, 1); } - void set_wm_mask (const std::string& path) { load (path, 2); } - void set_csf_mask (const std::string& path) { load (path, 3); } - void set_path_mask (const std::string& path) { load (path, 4); } - void set_none_mask (const std::string& path) { load (path, 5); } - - - bool operator() (const Iterator& pos) - { - assign_pos_of (pos, 0, 3).to (v_out); - bool voxel_nulled = false; - if (buffers[5].valid()) { - assign_pos_of (pos, 0, 3).to (buffers[5]); - if (buffers[5].value()) { - for (auto i = Loop(3) (v_out); i; ++i) - v_out.value() = 0.0; - voxel_nulled = true; - } +class Modifier { +public: + Modifier(Image &input_image, Image &output_image) : v_in(input_image), v_out(output_image) {} + + void set_cgm_mask(const std::string &path) { load(path, 0); } + void set_sgm_mask(const std::string &path) { load(path, 1); } + void set_wm_mask(const std::string &path) { load(path, 2); } + void set_csf_mask(const std::string &path) { load(path, 3); } + void set_path_mask(const std::string &path) { load(path, 4); } + void set_none_mask(const std::string &path) { load(path, 5); } + + bool operator()(const Iterator &pos) { + assign_pos_of(pos, 0, 3).to(v_out); + bool voxel_nulled = false; + if (buffers[5].valid()) { + assign_pos_of(pos, 0, 3).to(buffers[5]); + if (buffers[5].value()) { + for (auto i = Loop(3)(v_out); i; ++i) + v_out.value() = 0.0; + voxel_nulled = true; } - if (!voxel_nulled) { - unsigned int count = 0; - float values[5] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; - for (size_t tissue = 0; tissue != 5; ++tissue) { - if (buffers[tissue].valid()) { - assign_pos_of (pos, 0, 3).to (buffers[tissue]); - if (buffers[tissue].value()) { - ++count; - values[tissue] = 1.0; - } + } + if (!voxel_nulled) { + unsigned int count = 0; + float values[5] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + for (size_t tissue = 0; tissue != 5; ++tissue) { + if (buffers[tissue].valid()) { + assign_pos_of(pos, 0, 3).to(buffers[tissue]); + if (buffers[tissue].value()) { + ++count; + values[tissue] = 1.0; } } - if (count) { - if (count > 1) { - const float multiplier = 1.0 / float(count); - for (size_t tissue = 0; tissue != 5; ++tissue) - values[tissue] *= multiplier; - } - for (auto i = Loop(3) (v_out); i; ++i) - v_out.value() = values[v_out.index(3)]; - } else { - assign_pos_of (pos, 0, 3).to (v_in); - for (auto i = Loop(3) (v_in, v_out); i; ++i) - v_out.value() = v_in.value(); + } + if (count) { + if (count > 1) { + const float multiplier = 1.0 / float(count); + for (size_t tissue = 0; tissue != 5; ++tissue) + values[tissue] *= multiplier; } + for (auto i = Loop(3)(v_out); i; ++i) + v_out.value() = values[v_out.index(3)]; + } else { + assign_pos_of(pos, 0, 3).to(v_in); + for (auto i = Loop(3)(v_in, v_out); i; ++i) + v_out.value() = v_in.value(); } - return true; } - - - private: - Image v_in, v_out; - Image buffers[6]; - - void load (const std::string& path, const size_t index) - { - assert (index <= 5); - buffers[index] = Image::open (path); - if (!dimensions_match (v_in, buffers[index], 0, 3)) - throw Exception ("Image " + str(path) + " does not match 5TT image dimensions"); - } - + return true; + } + +private: + Image v_in, v_out; + Image buffers[6]; + + void load(const std::string &path, const size_t index) { + assert(index <= 5); + buffers[index] = Image::open(path); + if (!dimensions_match(v_in, buffers[index], 0, 3)) + throw Exception("Image " + str(path) + " does not match 5TT image dimensions"); + } }; - - - -void run () -{ - - auto in = Image::open (argument[0]); - DWI::Tractography::ACT::verify_5TT_image (in); - auto out = Image::create (argument[1], in); - - Modifier modifier (in, out); - - auto - opt = get_options ("cgm"); if (opt.size()) modifier.set_cgm_mask (opt[0][0]); - opt = get_options ("sgm"); if (opt.size()) modifier.set_sgm_mask (opt[0][0]); - opt = get_options ("wm"); if (opt.size()) modifier.set_wm_mask (opt[0][0]); - opt = get_options ("csf"); if (opt.size()) modifier.set_csf_mask (opt[0][0]); - opt = get_options ("path"); if (opt.size()) modifier.set_path_mask (opt[0][0]); - opt = get_options ("none"); if (opt.size()) modifier.set_none_mask (opt[0][0]); - - ThreadedLoop ("Modifying ACT 5TT image", in, 0, 3, 2).run (modifier); - +void run() { + + auto in = Image::open(argument[0]); + DWI::Tractography::ACT::verify_5TT_image(in); + auto out = Image::create(argument[1], in); + + Modifier modifier(in, out); + + auto opt = get_options("cgm"); + if (opt.size()) + modifier.set_cgm_mask(opt[0][0]); + opt = get_options("sgm"); + if (opt.size()) + modifier.set_sgm_mask(opt[0][0]); + opt = get_options("wm"); + if (opt.size()) + modifier.set_wm_mask(opt[0][0]); + opt = get_options("csf"); + if (opt.size()) + modifier.set_csf_mask(opt[0][0]); + opt = get_options("path"); + if (opt.size()) + modifier.set_path_mask(opt[0][0]); + opt = get_options("none"); + if (opt.size()) + modifier.set_none_mask(opt[0][0]); + + ThreadedLoop("Modifying ACT 5TT image", in, 0, 3, 2).run(modifier); } - diff --git a/cmd/afdconnectivity.cpp b/cmd/afdconnectivity.cpp index 13c903b7ee..0c36c7ac24 100644 --- a/cmd/afdconnectivity.cpp +++ b/cmd/afdconnectivity.cpp @@ -15,159 +15,142 @@ */ #include "command.h" -#include "memory.h" -#include "version.h" #include "dwi/fmls.h" +#include "dwi/tractography/SIFT/model_base.h" #include "dwi/tractography/file.h" -#include "dwi/tractography/properties.h" #include "dwi/tractography/mapping/loader.h" #include "dwi/tractography/mapping/mapper.h" #include "dwi/tractography/mapping/mapping.h" -#include "dwi/tractography/SIFT/model_base.h" - +#include "dwi/tractography/properties.h" +#include "memory.h" +#include "version.h" using namespace MR; using namespace MR::DWI; using namespace App; -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Obtain an estimate of fibre connectivity between two regions using AFD and streamlines tractography"; DESCRIPTION - + "This estimate is obtained by determining a fibre volume (AFD) occupied by the pathway " - "of interest, and dividing by the streamline length." + +"This estimate is obtained by determining a fibre volume (AFD) occupied by the pathway " + "of interest, and dividing by the streamline length." - + "If only the streamlines belonging to the pathway of interest are provided, then " - "ALL of the fibre volume within each fixel selected will contribute to the result. " - "If the -wbft option is used to provide whole-brain fibre-tracking (of which the pathway of " - "interest should contain a subset), only the fraction of the fibre volume in each fixel " - "estimated to belong to the pathway of interest will contribute to the result." + + "If only the streamlines belonging to the pathway of interest are provided, then " + "ALL of the fibre volume within each fixel selected will contribute to the result. " + "If the -wbft option is used to provide whole-brain fibre-tracking (of which the pathway of " + "interest should contain a subset), only the fraction of the fibre volume in each fixel " + "estimated to belong to the pathway of interest will contribute to the result." - + "Use -quiet to suppress progress messages and output fibre connectivity value only." + + "Use -quiet to suppress progress messages and output fibre connectivity value only." - + "For valid comparisons of AFD connectivity across scans, images MUST be intensity " - "normalised and bias field corrected, and a common response function for all subjects " - "must be used." + + "For valid comparisons of AFD connectivity across scans, images MUST be intensity " + "normalised and bias field corrected, and a common response function for all subjects " + "must be used." - + "Note that the sum of the AFD is normalised by streamline length to " - "account for subject differences in fibre bundle length. This normalisation results in a measure " - "that is more related to the cross-sectional volume of the tract (and therefore 'connectivity'). " - "Note that SIFT-ed tract count is a superior measure because it is unaffected by tangential yet unrelated " - "fibres. However, AFD connectivity may be used as a substitute when Anatomically Constrained Tractography " - "is not possible due to uncorrectable EPI distortions, and SIFT may therefore not be as effective." - - + "Longer discussion regarding this command can additionally be found at: " - "https://mrtrix.readthedocs.io/en/" MRTRIX_BASE_VERSION "/concepts/afd_connectivity.html " - "(as well as in the relevant reference)."; + + "Note that the sum of the AFD is normalised by streamline length to " + "account for subject differences in fibre bundle length. This normalisation results in a measure " + "that is more related to the cross-sectional volume of the tract (and therefore 'connectivity'). " + "Note that SIFT-ed tract count is a superior measure because it is unaffected by tangential yet unrelated " + "fibres. However, AFD connectivity may be used as a substitute when Anatomically Constrained Tractography " + "is not possible due to uncorrectable EPI distortions, and SIFT may therefore not be as effective." + + "Longer discussion regarding this command can additionally be found at: " + "https://mrtrix.readthedocs.io/en/" MRTRIX_BASE_VERSION "/concepts/afd_connectivity.html " + "(as well as in the relevant reference)."; REFERENCES - + "Smith, R. E.; Raffelt, D.; Tournier, J.-D.; Connelly, A. " // Internal - "Quantitative Streamlines Tractography: Methods and Inter-Subject Normalisation. " - "Open Science Framework, https://doi.org/10.31219/osf.io/c67kn."; - + +"Smith, R. E.; Raffelt, D.; Tournier, J.-D.; Connelly, A. " // Internal + "Quantitative Streamlines Tractography: Methods and Inter-Subject Normalisation. " + "Open Science Framework, https://doi.org/10.31219/osf.io/c67kn."; ARGUMENTS - + Argument ("image", "the input FOD image.").type_image_in() + +Argument("image", "the input FOD image.").type_image_in() - + Argument ("tracks", "the input track file defining the bundle of interest.").type_tracks_in(); + + Argument("tracks", "the input track file defining the bundle of interest.").type_tracks_in(); OPTIONS - + Option ("wbft", "provide a whole-brain fibre-tracking data set (of which the input track file " - "should be a subset), to improve the estimate of fibre bundle volume in the " - "presence of partial volume") - + Argument ("tracks").type_tracks_in() - - + Option ("afd_map", "output a 3D image containing the AFD estimated for each voxel.") - + Argument ("image").type_image_out() - - + Option ("all_fixels", "if whole-brain fibre-tracking is NOT provided, then if multiple fixels within " - "a voxel are traversed by the pathway of interest, by default the fixel with the " - "greatest streamlines density is selected to contribute to the AFD in that voxel. " - "If this option is provided, then ALL fixels with non-zero streamlines density " - "will contribute to the result, even if multiple fixels per voxel are selected."); - + +Option("wbft", + "provide a whole-brain fibre-tracking data set (of which the input track file " + "should be a subset), to improve the estimate of fibre bundle volume in the " + "presence of partial volume") + + Argument("tracks").type_tracks_in() + + + Option("afd_map", "output a 3D image containing the AFD estimated for each voxel.") + + Argument("image").type_image_out() + + + Option("all_fixels", + "if whole-brain fibre-tracking is NOT provided, then if multiple fixels within " + "a voxel are traversed by the pathway of interest, by default the fixel with the " + "greatest streamlines density is selected to contribute to the AFD in that voxel. " + "If this option is provided, then ALL fixels with non-zero streamlines density " + "will contribute to the result, even if multiple fixels per voxel are selected."); } - using value_type = float; using DWI::Tractography::Mapping::SetDixel; using DWI::Tractography::SIFT::FixelBase; +class AFDConnFixel : public FixelBase { +public: + AFDConnFixel() : FixelBase(), length(0.0) {} + AFDConnFixel(const FMLS::FOD_lobe &lobe) : FixelBase(lobe), length(0.0) {} + AFDConnFixel(const AFDConnFixel &that) : FixelBase(that), length(that.length) {} + void add_to_selection(const value_type l) { length += l; } + value_type get_selected_volume(const value_type l) const { return get_TD() ? (get_FOD() * (l / get_TD())) : 0.0; } + value_type get_selected_volume() const { return get_TD() ? (get_FOD() * (length / get_TD())) : 0.0; } + value_type get_selected_length() const { return length; } + bool is_selected() const { return length; } -class AFDConnFixel : public FixelBase -{ - public: - AFDConnFixel () : FixelBase (), length (0.0) { } - AFDConnFixel (const FMLS::FOD_lobe& lobe) : FixelBase (lobe), length (0.0) { } - AFDConnFixel (const AFDConnFixel& that) : FixelBase (that), length (that.length) { } - - void add_to_selection (const value_type l) { length += l; } - value_type get_selected_volume (const value_type l) const { return get_TD() ? (get_FOD() * (l / get_TD())) : 0.0; } - value_type get_selected_volume () const { return get_TD() ? (get_FOD() * (length / get_TD())) : 0.0; } - value_type get_selected_length() const { return length; } - bool is_selected() const { return length; } - - private: - value_type length; - +private: + value_type length; }; - - - -class AFDConnectivity : public DWI::Tractography::SIFT::ModelBase -{ - public: - AFDConnectivity (Image& fod_buffer, const DWI::Directions::FastLookupSet& dirs, const std::string& tck_path, const std::string& wbft_path) : - DWI::Tractography::SIFT::ModelBase (fod_buffer, dirs), - have_wbft (wbft_path.size()), - all_fixels (false), - mapper (fod_buffer, dirs), - v_fod (fod_buffer) - { - if (have_wbft) { - perform_FOD_segmentation (fod_buffer); - map_streamlines (wbft_path); - } else { - fmls.reset (new DWI::FMLS::Segmenter (dirs, Math::SH::LforN (fod_buffer.size (3)))); - } - mapper.set_upsample_ratio (DWI::Tractography::Mapping::determine_upsample_ratio (fod_buffer, tck_path, 0.1)); - mapper.set_use_precise_mapping (true); +class AFDConnectivity : public DWI::Tractography::SIFT::ModelBase { +public: + AFDConnectivity(Image &fod_buffer, + const DWI::Directions::FastLookupSet &dirs, + const std::string &tck_path, + const std::string &wbft_path) + : DWI::Tractography::SIFT::ModelBase(fod_buffer, dirs), + have_wbft(wbft_path.size()), + all_fixels(false), + mapper(fod_buffer, dirs), + v_fod(fod_buffer) { + if (have_wbft) { + perform_FOD_segmentation(fod_buffer); + map_streamlines(wbft_path); + } else { + fmls.reset(new DWI::FMLS::Segmenter(dirs, Math::SH::LforN(fod_buffer.size(3)))); } + mapper.set_upsample_ratio(DWI::Tractography::Mapping::determine_upsample_ratio(fod_buffer, tck_path, 0.1)); + mapper.set_use_precise_mapping(true); + } + void set_all_fixels(const bool i) { all_fixels = i; } - void set_all_fixels (const bool i) { all_fixels = i; } - - - value_type get (const std::string& path); - void save (const std::string& path); - - - private: - const bool have_wbft; - bool all_fixels; - DWI::Tractography::Mapping::TrackMapperBase mapper; - Image v_fod; - std::unique_ptr fmls; + value_type get(const std::string &path); + void save(const std::string &path); - using Fixel_map::accessor; +private: + const bool have_wbft; + bool all_fixels; + DWI::Tractography::Mapping::TrackMapperBase mapper; + Image v_fod; + std::unique_ptr fmls; + using Fixel_map::accessor; }; - - -value_type AFDConnectivity::get (const std::string& path) -{ +value_type AFDConnectivity::get(const std::string &path) { Tractography::Properties properties; - Tractography::Reader reader (path, properties); - const size_t track_count = (properties.find ("count") == properties.end() ? 0 : to(properties["count"])); - DWI::Tractography::Mapping::TrackLoader loader (reader, track_count, "summing apparent fibre density within track"); + Tractography::Reader reader(path, properties); + const size_t track_count = (properties.find("count") == properties.end() ? 0 : to(properties["count"])); + DWI::Tractography::Mapping::TrackLoader loader(reader, track_count, "summing apparent fibre density within track"); // If WBFT is provided, this is the sum of (volume/length) across streamlines // Otherwise, it's a sum of lengths of all streamlines (for later scaling by mean streamline length) @@ -176,11 +159,11 @@ value_type AFDConnectivity::get (const std::string& path) size_t count = 0; Tractography::Streamline tck; - while (loader (tck)) { + while (loader(tck)) { ++count; SetDixel dixels; - mapper (tck, dixels); + mapper(tck, dixels); double this_length = 0.0, this_volume = 0.0; for (SetDixel::const_iterator i = dixels.begin(); i != dixels.end(); ++i) { @@ -191,38 +174,37 @@ value_type AFDConnectivity::get (const std::string& path) // run the segmenter if (!have_wbft) { - VoxelAccessor v (accessor()); - assign_pos_of (*i, 0, 3).to (v); + VoxelAccessor v(accessor()); + assign_pos_of(*i, 0, 3).to(v); if (!v.value()) { - assign_pos_of (*i, 0, 3).to (v_fod); + assign_pos_of(*i, 0, 3).to(v_fod); DWI::FMLS::SH_coefs fod_data; DWI::FMLS::FOD_lobes fod_lobes; - fod_data.vox[0] = v_fod.index (0); fod_data.vox[1] = v_fod.index (1); fod_data.vox[2] = v_fod.index (2); - fod_data.resize (v_fod.size (3)); - for (auto i = Loop(3) (v_fod); i; ++i) - fod_data[v_fod.index (3)] = v_fod.value(); - - (*fmls) (fod_data, fod_lobes); - (*this) (fod_lobes); + fod_data.vox[0] = v_fod.index(0); + fod_data.vox[1] = v_fod.index(1); + fod_data.vox[2] = v_fod.index(2); + fod_data.resize(v_fod.size(3)); + for (auto i = Loop(3)(v_fod); i; ++i) + fod_data[v_fod.index(3)] = v_fod.value(); + (*fmls)(fod_data, fod_lobes); + (*this)(fod_lobes); } } - const size_t fixel_index = dixel2fixel (*i); - AFDConnFixel& fixel = fixels[fixel_index]; - fixel.add_to_selection (i->get_length()); + const size_t fixel_index = dixel2fixel(*i); + AFDConnFixel &fixel = fixels[fixel_index]; + fixel.add_to_selection(i->get_length()); if (have_wbft) - this_volume += fixel.get_selected_volume (i->get_length()); - + this_volume += fixel.get_selected_volume(i->get_length()); } if (have_wbft) sum_contributions += (this_volume / this_length); else sum_contributions += this_length; - } if (!have_wbft) { @@ -241,11 +223,11 @@ value_type AFDConnectivity::get (const std::string& path) } else { // Only allow one fixel per voxel to contribute to the result - VoxelAccessor v (accessor()); - for (auto l = Loop(v) (v); l; ++l) { + VoxelAccessor v(accessor()); + for (auto l = Loop(v)(v); l; ++l) { if (v.value()) { value_type voxel_afd = 0.0, max_td = 0.0; - for (Fixel_map::Iterator i = begin (v); i; ++i) { + for (Fixel_map::Iterator i = begin(v); i; ++i) { if (i().get_selected_length() > max_td) { max_td = i().get_selected_length(); voxel_afd = i().get_FOD(); @@ -254,37 +236,31 @@ value_type AFDConnectivity::get (const std::string& path) sum_volumes += voxel_afd; } } - } // sum_contributions currently stores sum of streamline lengths; // turn into a mean length, then combine with volume to get a connectivity value const double mean_length = sum_contributions / double(count); sum_contributions = sum_volumes / mean_length; - } return sum_contributions; - } - - -void AFDConnectivity::save (const std::string& path) -{ - auto out = Image::create (path, Fixel_map::header()); - VoxelAccessor v (accessor()); - for (auto l = Loop(v) (v, out); l; ++l) { +void AFDConnectivity::save(const std::string &path) { + auto out = Image::create(path, Fixel_map::header()); + VoxelAccessor v(accessor()); + for (auto l = Loop(v)(v, out); l; ++l) { value_type value = 0.0; if (have_wbft) { - for (Fixel_map::Iterator i = begin (v); i; ++i) + for (Fixel_map::Iterator i = begin(v); i; ++i) value += i().get_selected_volume(); } else if (all_fixels) { - for (Fixel_map::Iterator i = begin (v); i; ++i) + for (Fixel_map::Iterator i = begin(v); i; ++i) value += (i().is_selected() ? i().get_FOD() : 0.0); } else { value_type max_td = 0.0; - for (Fixel_map::Iterator i = begin (v); i; ++i) { + for (Fixel_map::Iterator i = begin(v); i; ++i) { if (i().get_selected_length() > max_td) { max_td = i().get_selected_length(); value = i().get_FOD(); @@ -295,29 +271,25 @@ void AFDConnectivity::save (const std::string& path) } } - - -void run () -{ - auto opt = get_options ("wbft"); +void run() { + auto opt = get_options("wbft"); const std::string wbft_path = opt.size() ? str(opt[0][0]) : ""; - DWI::Directions::FastLookupSet dirs (1281); - auto fod = Image::open (argument[0]); - Math::SH::check (fod); - check_3D_nonunity (fod); - AFDConnectivity model (fod, dirs, argument[1], wbft_path); + DWI::Directions::FastLookupSet dirs(1281); + auto fod = Image::open(argument[0]); + Math::SH::check(fod); + check_3D_nonunity(fod); + AFDConnectivity model(fod, dirs, argument[1], wbft_path); - opt = get_options ("all_fixels"); - model.set_all_fixels (opt.size()); + opt = get_options("all_fixels"); + model.set_all_fixels(opt.size()); - const value_type connectivity_value = model.get (argument[1]); + const value_type connectivity_value = model.get(argument[1]); // output the AFD sum using std::cout. This enables output to be redirected to a file without the console output. std::cout << connectivity_value << std::endl; - opt = get_options ("afd_map"); + opt = get_options("afd_map"); if (opt.size()) - model.save (opt[0][0]); + model.save(opt[0][0]); } - diff --git a/cmd/amp2response.cpp b/cmd/amp2response.cpp index 192a29c89d..54a1cf4810 100644 --- a/cmd/amp2response.cpp +++ b/cmd/amp2response.cpp @@ -17,256 +17,242 @@ #include #include "command.h" -#include "header.h" -#include "image.h" -#include "image_helpers.h" -#include "types.h" #include "dwi/gradient.h" #include "dwi/shells.h" #include "file/matrix.h" +#include "header.h" +#include "image.h" +#include "image_helpers.h" +#include "math/SH.h" +#include "math/ZSH.h" #include "math/constrained_least_squares.h" #include "math/rng.h" #include "math/sphere.h" -#include "math/SH.h" -#include "math/ZSH.h" - - - +#include "types.h" using namespace MR; using namespace App; - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au) and J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Estimate response function coefficients based on the DWI signal in single-fibre voxels"; DESCRIPTION - + "This command uses the image data from all selected single-fibre voxels concurrently, " - "rather than simply averaging their individual spherical harmonic coefficients. It also " - "ensures that the response function is non-negative, and monotonic (i.e. its amplitude " - "must increase from the fibre direction out to the orthogonal plane)." + +"This command uses the image data from all selected single-fibre voxels concurrently, " + "rather than simply averaging their individual spherical harmonic coefficients. It also " + "ensures that the response function is non-negative, and monotonic (i.e. its amplitude " + "must increase from the fibre direction out to the orthogonal plane)." - + "If multi-shell data are provided, and one or more b-value shells are not explicitly " - "requested, the command will generate a response function for every b-value shell " - "(including b=0 if present)."; + + "If multi-shell data are provided, and one or more b-value shells are not explicitly " + "requested, the command will generate a response function for every b-value shell " + "(including b=0 if present)."; ARGUMENTS - + Argument ("amps", "the amplitudes image").type_image_in() - + Argument ("mask", "the mask containing the voxels from which to estimate the response function").type_image_in() - + Argument ("directions", "a 4D image containing the estimated fibre directions").type_image_in() - + Argument ("response", "the output zonal spherical harmonic coefficients").type_file_out(); + +Argument("amps", "the amplitudes image").type_image_in() + + Argument("mask", "the mask containing the voxels from which to estimate the response function").type_image_in() + + Argument("directions", "a 4D image containing the estimated fibre directions").type_image_in() + + Argument("response", "the output zonal spherical harmonic coefficients").type_file_out(); OPTIONS - + Option ("isotropic", "estimate an isotropic response function (lmax=0 for all shells)") + +Option("isotropic", "estimate an isotropic response function (lmax=0 for all shells)") - + Option ("noconstraint", "disable the non-negativity and monotonicity constraints") + + Option("noconstraint", "disable the non-negativity and monotonicity constraints") - + Option ("directions", "provide an external text file containing the directions along which the amplitudes are sampled") - + Argument("path").type_file_in() + + Option("directions", + "provide an external text file containing the directions along which the amplitudes are sampled") + + Argument("path").type_file_in() - + DWI::ShellsOption + + DWI::ShellsOption - + Option ("lmax", "specify the maximum harmonic degree of the response function to estimate " - "(can be a comma-separated list for multi-shell data)") - + Argument ("values").type_sequence_int(); + + Option("lmax", + "specify the maximum harmonic degree of the response function to estimate " + "(can be a comma-separated list for multi-shell data)") + + Argument("values").type_sequence_int(); REFERENCES - + "Smith, R. E.; Dhollander, T. & Connelly, A. " // Internal - "Constrained linear least squares estimation of anisotropic response function for spherical deconvolution. " - "ISMRM Workshop on Breaking the Barriers of Diffusion MRI, 23."; + +"Smith, R. E.; Dhollander, T. & Connelly, A. " // Internal + "Constrained linear least squares estimation of anisotropic response function for spherical deconvolution. " + "ISMRM Workshop on Breaking the Barriers of Diffusion MRI, 23."; } - - -Eigen::Matrix gen_rotation_matrix (const Eigen::Vector3d& dir) -{ +Eigen::Matrix gen_rotation_matrix(const Eigen::Vector3d &dir) { static Math::RNG::Normal rng; // Generates a matrix that will rotate a unit vector into a new frame of reference, // where the peak direction of the FOD is aligned in Z (3rd dimension) // Previously this was done using the tensor eigenvectors // Here the other two axes are determined at random (but both are orthogonal to the FOD peak direction) Eigen::Matrix R; - R (2, 0) = dir[0]; R (2, 1) = dir[1]; R (2, 2) = dir[2]; - Eigen::Vector3d vec2 (rng(), rng(), rng()); - vec2 = dir.cross (vec2); + R(2, 0) = dir[0]; + R(2, 1) = dir[1]; + R(2, 2) = dir[2]; + Eigen::Vector3d vec2(rng(), rng(), rng()); + vec2 = dir.cross(vec2); vec2.normalize(); - R (0, 0) = vec2[0]; R (0, 1) = vec2[1]; R (0, 2) = vec2[2]; - Eigen::Vector3d vec3 = dir.cross (vec2); + R(0, 0) = vec2[0]; + R(0, 1) = vec2[1]; + R(0, 2) = vec2[2]; + Eigen::Vector3d vec3 = dir.cross(vec2); vec3.normalize(); - R (1, 0) = vec3[0]; R (1, 1) = vec3[1]; R (1, 2) = vec3[2]; + R(1, 0) = vec3[0]; + R(1, 1) = vec3[1]; + R(1, 2) = vec3[2]; return R; } - -vector all_volumes (const size_t num) -{ - vector result (num); +vector all_volumes(const size_t num) { + vector result(num); for (size_t i = 0; i != num; ++i) result[i] = i; return result; } - - - -class Accumulator { +class Accumulator { +public: + class Shared { public: - class Shared { - public: - Shared (int lmax, const vector& volumes, const Eigen::MatrixXd& dirs) : - lmax (lmax), - dirs (dirs), - volumes (volumes), - M (Eigen::MatrixXd::Zero (Math::ZSH::NforL (lmax), Math::ZSH::NforL (lmax))), - b (Eigen::VectorXd::Zero (M.rows())), - count (0) { } - - const int lmax; - const Eigen::MatrixXd& dirs; - const vector& volumes; - Eigen::MatrixXd M; - Eigen::VectorXd b; - size_t count; - }; - - Accumulator (Shared& shared) : - S (shared), - amplitudes (S.volumes.size()), - b (S.b), - M (S.M), - count (0), - rotated_dirs_cartesian (S.dirs.rows(), 3) { } - - ~Accumulator () - { - // accumulate results from all threads: - S.M += M; - S.b += b; - S.count += count; - } + Shared(int lmax, const vector &volumes, const Eigen::MatrixXd &dirs) + : lmax(lmax), + dirs(dirs), + volumes(volumes), + M(Eigen::MatrixXd::Zero(Math::ZSH::NforL(lmax), Math::ZSH::NforL(lmax))), + b(Eigen::VectorXd::Zero(M.rows())), + count(0) {} + + const int lmax; + const Eigen::MatrixXd &dirs; + const vector &volumes; + Eigen::MatrixXd M; + Eigen::VectorXd b; + size_t count; + }; - void operator() (Image& amp_image, Image& dir_image, Image& mask) - { - if (mask.value()) { - ++count; - - // Grab the fibre direction - Eigen::Vector3d fibre_dir; - for (dir_image.index(3) = 0; dir_image.index(3) != 3; ++dir_image.index(3)) - fibre_dir[dir_image.index(3)] = dir_image.value(); - fibre_dir.normalize(); - - // Rotate the directions into a new reference frame, - // where the Z axis is defined by the specified direction - auto R = gen_rotation_matrix (fibre_dir); - rotated_dirs_cartesian.transpose() = R * S.dirs.transpose(); - - // Convert directions from Euclidean space to azimuth/elevation pairs - Eigen::MatrixXd rotated_dirs_azel = Math::Sphere::cartesian2spherical (rotated_dirs_cartesian); - - // Constrain elevations to between 0 and pi/2 - for (ssize_t i = 0; i != rotated_dirs_azel.rows(); ++i) { - if (rotated_dirs_azel (i, 1) > Math::pi_2) { - if (rotated_dirs_azel (i, 0) > Math::pi) - rotated_dirs_azel (i, 0) -= Math::pi; - else - rotated_dirs_azel (i, 0) += Math::pi; - rotated_dirs_azel (i, 1) = Math::pi - rotated_dirs_azel (i, 1); - } - } + Accumulator(Shared &shared) + : S(shared), amplitudes(S.volumes.size()), b(S.b), M(S.M), count(0), rotated_dirs_cartesian(S.dirs.rows(), 3) {} - // Generate the ZSH -> amplitude transform - transform = Math::ZSH::init_amp_transform (rotated_dirs_azel.col(1), S.lmax); + ~Accumulator() { + // accumulate results from all threads: + S.M += M; + S.b += b; + S.count += count; + } - // Grab the image data - for (size_t i = 0; i != S.volumes.size(); ++i) { - amp_image.index(3) = S.volumes[i]; - amplitudes[i] = amp_image.value(); + void operator()(Image &_image, Image &dir_image, Image &mask) { + if (mask.value()) { + ++count; + + // Grab the fibre direction + Eigen::Vector3d fibre_dir; + for (dir_image.index(3) = 0; dir_image.index(3) != 3; ++dir_image.index(3)) + fibre_dir[dir_image.index(3)] = dir_image.value(); + fibre_dir.normalize(); + + // Rotate the directions into a new reference frame, + // where the Z axis is defined by the specified direction + auto R = gen_rotation_matrix(fibre_dir); + rotated_dirs_cartesian.transpose() = R * S.dirs.transpose(); + + // Convert directions from Euclidean space to azimuth/elevation pairs + Eigen::MatrixXd rotated_dirs_azel = Math::Sphere::cartesian2spherical(rotated_dirs_cartesian); + + // Constrain elevations to between 0 and pi/2 + for (ssize_t i = 0; i != rotated_dirs_azel.rows(); ++i) { + if (rotated_dirs_azel(i, 1) > Math::pi_2) { + if (rotated_dirs_azel(i, 0) > Math::pi) + rotated_dirs_azel(i, 0) -= Math::pi; + else + rotated_dirs_azel(i, 0) += Math::pi; + rotated_dirs_azel(i, 1) = Math::pi - rotated_dirs_azel(i, 1); } + } - // accumulate results: - b += transform.transpose() * amplitudes; - M.selfadjointView().rankUpdate (transform.transpose()); + // Generate the ZSH -> amplitude transform + transform = Math::ZSH::init_amp_transform(rotated_dirs_azel.col(1), S.lmax); + + // Grab the image data + for (size_t i = 0; i != S.volumes.size(); ++i) { + amp_image.index(3) = S.volumes[i]; + amplitudes[i] = amp_image.value(); } + + // accumulate results: + b += transform.transpose() * amplitudes; + M.selfadjointView().rankUpdate(transform.transpose()); } + } - protected: - Shared& S; - Eigen::VectorXd amplitudes, b; - Eigen::MatrixXd M, transform; - size_t count; - Eigen::Matrix rotated_dirs_cartesian; +protected: + Shared &S; + Eigen::VectorXd amplitudes, b; + Eigen::MatrixXd M, transform; + size_t count; + Eigen::Matrix rotated_dirs_cartesian; }; - -void run () -{ +void run() { // Get directions from either selecting a b-value shell, or the header, or external file - auto header = Header::open (argument[0]); + auto header = Header::open(argument[0]); // May be dealing with multiple shells vector dirs_azel; vector> volumes; std::unique_ptr shells; - auto opt = get_options ("directions"); + auto opt = get_options("directions"); if (opt.size()) { - dirs_azel.push_back (File::Matrix::load_matrix (opt[0][0])); - volumes.push_back (all_volumes (dirs_azel.size())); + dirs_azel.push_back(File::Matrix::load_matrix(opt[0][0])); + volumes.push_back(all_volumes(dirs_azel.size())); } else { - auto hit = header.keyval().find ("directions"); + auto hit = header.keyval().find("directions"); if (hit != header.keyval().end()) { vector dir_vector; - for (auto line : split_lines (hit->second)) { - auto v = parse_floats (line); - dir_vector.insert (dir_vector.end(), v.begin(), v.end()); + for (auto line : split_lines(hit->second)) { + auto v = parse_floats(line); + dir_vector.insert(dir_vector.end(), v.begin(), v.end()); } - Eigen::MatrixXd directions (dir_vector.size() / 2, 2); + Eigen::MatrixXd directions(dir_vector.size() / 2, 2); for (size_t i = 0; i < dir_vector.size(); i += 2) { - directions (i/2, 0) = dir_vector[i]; - directions (i/2, 1) = dir_vector[i+1]; + directions(i / 2, 0) = dir_vector[i]; + directions(i / 2, 1) = dir_vector[i + 1]; } - dirs_azel.push_back (std::move (directions)); - volumes.push_back (all_volumes (dirs_azel.size())); + dirs_azel.push_back(std::move(directions)); + volumes.push_back(all_volumes(dirs_azel.size())); } else { - auto grad = DWI::get_DW_scheme (header); - shells.reset (new DWI::Shells (grad)); - shells->select_shells (false, false, false); + auto grad = DWI::get_DW_scheme(header); + shells.reset(new DWI::Shells(grad)); + shells->select_shells(false, false, false); for (size_t i = 0; i != shells->count(); ++i) { - volumes.push_back ((*shells)[i].get_volumes()); - dirs_azel.push_back (DWI::gen_direction_matrix (grad, volumes.back())); + volumes.push_back((*shells)[i].get_volumes()); + dirs_azel.push_back(DWI::gen_direction_matrix(grad, volumes.back())); } } } vector lmax; uint32_t max_lmax = 0; - opt = get_options ("lmax"); + opt = get_options("lmax"); if (get_options("isotropic").size()) { for (size_t i = 0; i != dirs_azel.size(); ++i) - lmax.push_back (0); + lmax.push_back(0); max_lmax = 0; } else if (opt.size()) { - lmax = parse_ints (opt[0][0]); + lmax = parse_ints(opt[0][0]); if (lmax.size() != dirs_azel.size()) - throw Exception ("Number of lmax\'s specified (" + str(lmax.size()) + ") does not match number of b-value shells (" + str(dirs_azel.size()) + ")"); + throw Exception("Number of lmax\'s specified (" + str(lmax.size()) + + ") does not match number of b-value shells (" + str(dirs_azel.size()) + ")"); for (auto i : lmax) { - if (i%2) - throw Exception ("Values specified for lmax must be even"); - max_lmax = std::max (max_lmax, i); + if (i % 2) + throw Exception("Values specified for lmax must be even"); + max_lmax = std::max(max_lmax, i); } if ((*shells)[0].is_bzero() && lmax.front()) { - WARN ("Non-zero lmax requested for " + - ((*shells)[0].get_mean() ? - "first shell (mean b=" + str((*shells)[0].get_mean()) + "), which MRtrix3 has classified as b=0;" : - "b=0 shell;")); - WARN (" unless intended, this is likely to fail, as b=0 contains no orientation contrast"); + WARN("Non-zero lmax requested for " + + ((*shells)[0].get_mean() + ? "first shell (mean b=" + str((*shells)[0].get_mean()) + "), which MRtrix3 has classified as b=0;" + : "b=0 shell;")); + WARN(" unless intended, this is likely to fail, as b=0 contains no orientation contrast"); } } else { // Auto-fill lmax @@ -276,97 +262,94 @@ void run () // - UNLESS it's b=0, in which case force lmax=0 for (size_t i = 0; i != dirs_azel.size(); ++i) { if (!i && shells && shells->smallest().is_bzero()) - lmax.push_back (0); + lmax.push_back(0); else - lmax.push_back (10); + lmax.push_back(10); } max_lmax = (shells && shells->smallest().is_bzero() && lmax.size() == 1) ? 0 : 10; } auto image = header.get_image(); - auto mask = Image::open (argument[1]); - check_dimensions (image, mask, 0, 3); + auto mask = Image::open(argument[1]); + check_dimensions(image, mask, 0, 3); if (!(mask.ndim() == 3 || (mask.ndim() == 4 && mask.size(3) == 1))) - throw Exception ("input mask must be a 3D image"); - auto dir_image = Image::open (argument[2]); + throw Exception("input mask must be a 3D image"); + auto dir_image = Image::open(argument[2]); if (dir_image.ndim() < 4 || dir_image.size(3) < 3) - throw Exception ("input direction image \"" + std::string (argument[2]) + "\" does not have expected dimensions"); - check_dimensions (image, dir_image, 0, 3); + throw Exception("input direction image \"" + std::string(argument[2]) + "\" does not have expected dimensions"); + check_dimensions(image, dir_image, 0, 3); size_t num_voxels = 0; - for (auto l = Loop (mask, 0, 3) (mask); l; ++l) { + for (auto l = Loop(mask, 0, 3)(mask); l; ++l) { if (mask.value()) ++num_voxels; } if (!num_voxels) - throw Exception ("input mask does not contain any voxels"); + throw Exception("input mask does not contain any voxels"); - const bool use_ols = get_options ("noconstraint").size(); + const bool use_ols = get_options("noconstraint").size(); - CONSOLE (std::string("estimating response function using ") + - ( use_ols ? "ordinary" : "constrained" ) + - " least-squares from " + str(num_voxels) + " voxels"); + CONSOLE(std::string("estimating response function using ") + (use_ols ? "ordinary" : "constrained") + + " least-squares from " + str(num_voxels) + " voxels"); - - - - Eigen::MatrixXd responses (dirs_azel.size(), Math::ZSH::NforL (max_lmax)); + Eigen::MatrixXd responses(dirs_azel.size(), Math::ZSH::NforL(max_lmax)); for (size_t shell_index = 0; shell_index != dirs_azel.size(); ++shell_index) { // check the ZSH -> amplitude transform upfront: { - auto transform = Math::ZSH::init_amp_transform (dirs_azel[shell_index].col(1), lmax[shell_index]); + auto transform = Math::ZSH::init_amp_transform(dirs_azel[shell_index].col(1), lmax[shell_index]); if (!transform.allFinite()) { - Exception e ("Unable to construct A2SH transformation for shell b=" + str(int(std::round((*shells)[shell_index].get_mean()))) + ";"); - e.push_back (" lmax (" + str(lmax[shell_index]) + ") may be too large for this shell"); + Exception e("Unable to construct A2SH transformation for shell b=" + + str(int(std::round((*shells)[shell_index].get_mean()))) + ";"); + e.push_back(" lmax (" + str(lmax[shell_index]) + ") may be too large for this shell"); if (!shell_index && (*shells)[0].is_bzero()) - e.push_back (" (this appears to be a b=0 shell, and therefore lmax should be set to 0 for this shell)"); + e.push_back(" (this appears to be a b=0 shell, and therefore lmax should be set to 0 for this shell)"); throw e; } } + auto dirs_cartesian = Math::Sphere::spherical2cartesian(dirs_azel[shell_index]); - auto dirs_cartesian = Math::Sphere::spherical2cartesian (dirs_azel[shell_index]); - - Accumulator::Shared shared (lmax[shell_index], volumes[shell_index], dirs_cartesian); - ThreadedLoop(image, 0, 3).run (Accumulator (shared), image, dir_image, mask); + Accumulator::Shared shared(lmax[shell_index], volumes[shell_index], dirs_cartesian); + ThreadedLoop(image, 0, 3).run(Accumulator(shared), image, dir_image, mask); Eigen::VectorXd rf; // Is this anything other than an isotropic response? if (!lmax[shell_index] || use_ols) { - rf = shared.M.llt().solve (shared.b); + rf = shared.M.llt().solve(shared.b); } else { // Generate the constraint matrix - // We are going to both constrain the amplitudes to be non-negative, and constrain the derivatives to be non-negative + // We are going to both constrain the amplitudes to be non-negative, and constrain the derivatives to be + // non-negative const size_t num_angles_constraint = 90; - Eigen::VectorXd els (num_angles_constraint+1); + Eigen::VectorXd els(num_angles_constraint + 1); for (size_t i = 0; i <= num_angles_constraint; ++i) els[i] = default_type(i) * Math::pi / 180.0; - auto amp_transform = Math::ZSH::init_amp_transform (els, lmax[shell_index]); - auto deriv_transform = Math::ZSH::init_deriv_transform (els, lmax[shell_index]); + auto amp_transform = Math::ZSH::init_amp_transform(els, lmax[shell_index]); + auto deriv_transform = Math::ZSH::init_deriv_transform(els, lmax[shell_index]); - Eigen::MatrixXd constraints (amp_transform.rows() + deriv_transform.rows(), amp_transform.cols()); - constraints.block (0, 0, amp_transform.rows(), amp_transform.cols()) = amp_transform; - constraints.block (amp_transform.rows(), 0, deriv_transform.rows(), deriv_transform.cols()) = deriv_transform; + Eigen::MatrixXd constraints(amp_transform.rows() + deriv_transform.rows(), amp_transform.cols()); + constraints.block(0, 0, amp_transform.rows(), amp_transform.cols()) = amp_transform; + constraints.block(amp_transform.rows(), 0, deriv_transform.rows(), deriv_transform.cols()) = deriv_transform; // Initialise the problem solver - auto problem = Math::ICLS::Problem (shared.M, constraints, Eigen::VectorXd(), 0, 1e-10, 1e-10, 0, 0.0, true); - auto solver = Math::ICLS::Solver (problem); + auto problem = + Math::ICLS::Problem(shared.M, constraints, Eigen::VectorXd(), 0, 1e-10, 1e-10, 0, 0.0, true); + auto solver = Math::ICLS::Solver(problem); // Estimate the solution - const size_t niter = solver (rf, shared.b); - INFO ("constrained least-squares solver completed in " + str(niter) + " iterations"); - + const size_t niter = solver(rf, shared.b); + INFO("constrained least-squares solver completed in " + str(niter) + " iterations"); } - CONSOLE (" b=" + str((*shells)[shell_index].get_mean(), 4) + ": [" + str(rf.transpose().cast()) + "]"); + CONSOLE(" b=" + str((*shells)[shell_index].get_mean(), 4) + ": [" + str(rf.transpose().cast()) + "]"); - rf.conservativeResizeLike (Eigen::VectorXd::Zero (Math::ZSH::NforL (max_lmax))); + rf.conservativeResizeLike(Eigen::VectorXd::Zero(Math::ZSH::NforL(max_lmax))); responses.row(shell_index) = rf; } @@ -377,5 +360,5 @@ void run () line += "," + str((*shells)[i].get_mean()); keyvals["Shells"] = line; } - File::Matrix::save_matrix (responses, argument[3], keyvals); + File::Matrix::save_matrix(responses, argument[3], keyvals); } diff --git a/cmd/amp2sh.cpp b/cmd/amp2sh.cpp index 2b63831c18..0c7f37634c 100644 --- a/cmd/amp2sh.cpp +++ b/cmd/amp2sh.cpp @@ -14,256 +14,219 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "image.h" -#include "phase_encoding.h" -#include "progressbar.h" #include "algo/threaded_loop.h" +#include "command.h" #include "dwi/gradient.h" #include "dwi/shells.h" #include "file/matrix.h" +#include "image.h" #include "math/SH.h" - +#include "phase_encoding.h" +#include "progressbar.h" using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Convert a set of amplitudes (defined along a set of corresponding directions) " - "to their spherical harmonic representation"; + "to their spherical harmonic representation"; DESCRIPTION - + "The spherical harmonic decomposition is calculated by least-squares linear fitting " - "to the amplitude data." + +"The spherical harmonic decomposition is calculated by least-squares linear fitting " + "to the amplitude data." - + "The directions can be defined either as a DW gradient scheme (for example to compute " - "the SH representation of the DW signal), a set of [az el] pairs as output by the dirgen " - "command, or a set of [ x y z ] directions in Cartesian coordinates. The DW " - "gradient scheme or direction set can be supplied within the input image " - "header or using the -gradient or -directions option. Note that if a " - "direction set and DW gradient scheme can be found, the direction set " - "will be used by default." + + "The directions can be defined either as a DW gradient scheme (for example to compute " + "the SH representation of the DW signal), a set of [az el] pairs as output by the dirgen " + "command, or a set of [ x y z ] directions in Cartesian coordinates. The DW " + "gradient scheme or direction set can be supplied within the input image " + "header or using the -gradient or -directions option. Note that if a " + "direction set and DW gradient scheme can be found, the direction set " + "will be used by default." - + Math::SH::encoding_description; + + Math::SH::encoding_description; ARGUMENTS - + Argument ("amp", "the input amplitude image.").type_image_in () - + Argument ("SH", "the output spherical harmonics coefficients image.").type_image_out (); - + +Argument("amp", "the input amplitude image.").type_image_in() + + Argument("SH", "the output spherical harmonics coefficients image.").type_image_out(); OPTIONS - + Option ("lmax", - "set the maximum harmonic order for the output series. By default, the " - "program will use the highest possible lmax given the number of " - "diffusion-weighted images, up to a maximum of 8.") - + Argument ("order").type_integer (0, 30) - - + Option ("normalise", "normalise the DW signal to the b=0 image") - - + Option ("directions", "the directions corresponding to the input amplitude image used to sample AFD. " - "By default this option is not required providing the direction set is supplied " - "in the amplitude image. This should be supplied as a list of directions [az el], " - "as generated using the dirgen command, or as a list of [ x y z ] Cartesian coordinates.") - + Argument ("file").type_file_in() - - + Option ("rician", "correct for Rician noise induced bias, using noise map supplied") - + Argument ("noise").type_image_in() - - + DWI::GradImportOptions() - + DWI::ShellsOption - + Stride::Options; -} + +Option("lmax", + "set the maximum harmonic order for the output series. By default, the " + "program will use the highest possible lmax given the number of " + "diffusion-weighted images, up to a maximum of 8.") + + Argument("order").type_integer(0, 30) + + Option("normalise", "normalise the DW signal to the b=0 image") + + Option("directions", + "the directions corresponding to the input amplitude image used to sample AFD. " + "By default this option is not required providing the direction set is supplied " + "in the amplitude image. This should be supplied as a list of directions [az el], " + "as generated using the dirgen command, or as a list of [ x y z ] Cartesian coordinates.") + + Argument("file").type_file_in() -#define RICIAN_POWER 2.25 + + Option("rician", "correct for Rician noise induced bias, using noise map supplied") + + Argument("noise").type_image_in() + + + DWI::GradImportOptions() + DWI::ShellsOption + Stride::Options; +} +#define RICIAN_POWER 2.25 using value_type = float; -class Amp2SHCommon { - public: - template - Amp2SHCommon (const MatrixType& sh2amp, - const vector& bzeros, - const vector& dwis, - bool normalise_to_bzero) : - sh2amp (sh2amp), - amp2sh (Math::pinv (sh2amp)), - bzeros (bzeros), - dwis (dwis), - normalise (normalise_to_bzero) { } - - - Eigen::MatrixXd sh2amp, amp2sh; - const vector& bzeros; - const vector& dwis; - bool normalise; +class Amp2SHCommon { +public: + template + Amp2SHCommon(const MatrixType &sh2amp, + const vector &bzeros, + const vector &dwis, + bool normalise_to_bzero) + : sh2amp(sh2amp), amp2sh(Math::pinv(sh2amp)), bzeros(bzeros), dwis(dwis), normalise(normalise_to_bzero) {} + + Eigen::MatrixXd sh2amp, amp2sh; + const vector &bzeros; + const vector &dwis; + bool normalise; }; +class Amp2SH { +public: + Amp2SH(const Amp2SHCommon &common) + : C(common), a(common.amp2sh.cols()), s(common.amp2sh.rows()), c(common.amp2sh.rows()) {} + template void operator()(SHImageType &SH, AmpImageType &) { + get_amps(amp); + c.noalias() = C.amp2sh * a; + write_SH(SH); + } + // Rician-corrected version: + template + void operator()(SHImageType &SH, AmpImageType &, const NoiseImageType &noise) { + w = Eigen::VectorXd::Ones(C.sh2amp.rows()); + + get_amps(amp); + c = C.amp2sh * a; + + for (size_t iter = 0; iter < 20; ++iter) { + sh2amp = C.sh2amp; + if (get_rician_bias(sh2amp, noise.value())) + break; + for (ssize_t n = 0; n < sh2amp.rows(); ++n) + sh2amp.row(n).array() *= w[n]; + + s.noalias() = sh2amp.transpose() * ap; + Q.triangularView() = sh2amp.transpose() * sh2amp; + llt.compute(Q); + c = llt.solve(s); + } -class Amp2SH { - public: - Amp2SH (const Amp2SHCommon& common) : - C (common), - a (common.amp2sh.cols()), - s (common.amp2sh.rows()), - c (common.amp2sh.rows()) { } - - template - void operator() (SHImageType& SH, AmpImageType& amp) - { - get_amps (amp); - c.noalias() = C.amp2sh * a; - write_SH (SH); - } - - - - // Rician-corrected version: - template - void operator() (SHImageType& SH, AmpImageType& amp, const NoiseImageType& noise) - { - w = Eigen::VectorXd::Ones (C.sh2amp.rows()); - - get_amps (amp); - c = C.amp2sh * a; - - for (size_t iter = 0; iter < 20; ++iter) { - sh2amp = C.sh2amp; - if (get_rician_bias (sh2amp, noise.value())) - break; - for (ssize_t n = 0; n < sh2amp.rows(); ++n) - sh2amp.row (n).array() *= w[n]; - - s.noalias() = sh2amp.transpose() * ap; - Q.triangularView() = sh2amp.transpose() * sh2amp; - llt.compute (Q); - c = llt.solve (s); - } + write_SH(SH); + } - write_SH (SH); +protected: + const Amp2SHCommon &C; + Eigen::VectorXd a, s, c, w, ap; + Eigen::MatrixXd Q, sh2amp; + Eigen::LLT llt; + + template void get_amps(AmpImageType &) { + double norm = 1.0; + if (C.normalise) { + for (size_t n = 0; n < C.bzeros.size(); n++) { + amp.index(3) = C.bzeros[n]; + norm += amp.value(); } + norm = C.bzeros.size() / norm; + } - protected: - const Amp2SHCommon& C; - Eigen::VectorXd a, s, c, w, ap; - Eigen::MatrixXd Q, sh2amp; - Eigen::LLT llt; - - template - void get_amps (AmpImageType& amp) { - double norm = 1.0; - if (C.normalise) { - for (size_t n = 0; n < C.bzeros.size(); n++) { - amp.index(3) = C.bzeros[n]; - norm += amp.value (); - } - norm = C.bzeros.size() / norm; - } - - for (ssize_t n = 0; n < a.size(); n++) { - amp.index(3) = C.dwis.size() ? C.dwis[n] : n; - a[n] = amp.value() * norm; - } - } + for (ssize_t n = 0; n < a.size(); n++) { + amp.index(3) = C.dwis.size() ? C.dwis[n] : n; + a[n] = amp.value() * norm; + } + } - template - void write_SH (SHImageType& SH) { - for (auto l = Loop(3) (SH); l; ++l) - SH.value() = c[SH.index(3)]; - } + template void write_SH(SHImageType &SH) { + for (auto l = Loop(3)(SH); l; ++l) + SH.value() = c[SH.index(3)]; + } - bool get_rician_bias (const Eigen::MatrixXd& sh2amp, default_type noise) { - ap = sh2amp * c; - default_type norm_diff = 0.0; - default_type norm_amp = 0.0; - for (ssize_t n = 0; n < ap.size() ; ++n) { - ap[n] = std::max (ap[n], default_type(0.0)); - default_type t = std::pow (ap[n]/noise, default_type(RICIAN_POWER)); - w[n] = Math::pow2 ((t + 1.7)/(t + 1.12)); - default_type diff = a[n] - noise * std::pow (t + 1.65, 1.0/RICIAN_POWER); - norm_diff += Math::pow2 (diff); - norm_amp += Math::pow2 (a[n]); - ap[n] += diff; - } - return norm_diff/norm_amp < 1.0e-8; + bool get_rician_bias(const Eigen::MatrixXd &sh2amp, default_type noise) { + ap = sh2amp * c; + default_type norm_diff = 0.0; + default_type norm_amp = 0.0; + for (ssize_t n = 0; n < ap.size(); ++n) { + ap[n] = std::max(ap[n], default_type(0.0)); + default_type t = std::pow(ap[n] / noise, default_type(RICIAN_POWER)); + w[n] = Math::pow2((t + 1.7) / (t + 1.12)); + default_type diff = a[n] - noise * std::pow(t + 1.65, 1.0 / RICIAN_POWER); + norm_diff += Math::pow2(diff); + norm_amp += Math::pow2(a[n]); + ap[n] += diff; } + return norm_diff / norm_amp < 1.0e-8; + } }; - - - - -void run () -{ - auto amp = Image::open (argument[0]).with_direct_io (3); - Header header (amp); +void run() { + auto amp = Image::open(argument[0]).with_direct_io(3); + Header header(amp); vector bzeros, dwis; Eigen::MatrixXd dirs; - auto opt = get_options ("directions"); + auto opt = get_options("directions"); if (opt.size()) { - dirs = File::Matrix::load_matrix (opt[0][0]); + dirs = File::Matrix::load_matrix(opt[0][0]); if (dirs.cols() == 3) - dirs = Math::Sphere::cartesian2spherical (dirs); - } - else { - auto hit = header.keyval().find ("directions"); + dirs = Math::Sphere::cartesian2spherical(dirs); + } else { + auto hit = header.keyval().find("directions"); if (hit != header.keyval().end()) { vector dir_vector; - for (auto line : split_lines (hit->second)) { - auto v = parse_floats (line); - dir_vector.insert (dir_vector.end(), v.begin(), v.end()); + for (auto line : split_lines(hit->second)) { + auto v = parse_floats(line); + dir_vector.insert(dir_vector.end(), v.begin(), v.end()); } dirs.resize(dir_vector.size() / 2, 2); for (size_t i = 0; i < dir_vector.size(); i += 2) { - dirs(i/2, 0) = dir_vector[i]; - dirs(i/2, 1) = dir_vector[i+1]; + dirs(i / 2, 0) = dir_vector[i]; + dirs(i / 2, 1) = dir_vector[i + 1]; } header.keyval()["basis_directions"] = hit->second; - header.keyval().erase (hit); - } - else { - auto grad = DWI::get_DW_scheme (amp); - DWI::Shells shells (grad); - shells.select_shells (true, false, false); + header.keyval().erase(hit); + } else { + auto grad = DWI::get_DW_scheme(amp); + DWI::Shells shells(grad); + shells.select_shells(true, false, false); if (shells.smallest().is_bzero()) bzeros = shells.smallest().get_volumes(); dwis = shells.largest().get_volumes(); - dirs = DWI::gen_direction_matrix (grad, dwis); - DWI::stash_DW_scheme (header, grad); + dirs = DWI::gen_direction_matrix(grad, dwis); + DWI::stash_DW_scheme(header, grad); } } - PhaseEncoding::clear_scheme (header); - - auto sh2amp = DWI::compute_SH2amp_mapping (dirs, true, 8); + PhaseEncoding::clear_scheme(header); + auto sh2amp = DWI::compute_SH2amp_mapping(dirs, true, 8); - bool normalise = get_options ("normalise").size(); + bool normalise = get_options("normalise").size(); if (normalise && !bzeros.size()) - throw Exception ("the normalise option is only available if the input data contains b=0 images."); + throw Exception("the normalise option is only available if the input data contains b=0 images."); + header.size(3) = sh2amp.cols(); + Stride::set_from_command_line(header); + auto SH = Image::create(argument[1], header); - header.size (3) = sh2amp.cols(); - Stride::set_from_command_line (header); - auto SH = Image::create (argument[1], header); + Amp2SHCommon common(sh2amp, bzeros, dwis, normalise); - Amp2SHCommon common (sh2amp, bzeros, dwis, normalise); - - opt = get_options ("rician"); + opt = get_options("rician"); if (opt.size()) { - auto noise = Image::open (opt[0][0]).with_direct_io(); - ThreadedLoop ("mapping amplitudes to SH coefficients", amp, 0, 3) - .run (Amp2SH (common), SH, amp, noise); - } - else { - ThreadedLoop ("mapping amplitudes to SH coefficients", amp, 0, 3) - .run (Amp2SH (common), SH, amp); + auto noise = Image::open(opt[0][0]).with_direct_io(); + ThreadedLoop("mapping amplitudes to SH coefficients", amp, 0, 3).run(Amp2SH(common), SH, amp, noise); + } else { + ThreadedLoop("mapping amplitudes to SH coefficients", amp, 0, 3).run(Amp2SH(common), SH, amp); } } diff --git a/cmd/connectome2tck.cpp b/cmd/connectome2tck.cpp index b9809c750a..c3ebee7b10 100644 --- a/cmd/connectome2tck.cpp +++ b/cmd/connectome2tck.cpp @@ -25,14 +25,12 @@ #include "connectome/connectome.h" -#include "dwi/tractography/file.h" -#include "dwi/tractography/properties.h" -#include "dwi/tractography/weights.h" #include "dwi/tractography/connectome/extract.h" #include "dwi/tractography/connectome/streamline.h" +#include "dwi/tractography/file.h" #include "dwi/tractography/mapping/loader.h" - - +#include "dwi/tractography/properties.h" +#include "dwi/tractography/weights.h" using namespace MR; using namespace App; @@ -41,158 +39,159 @@ using namespace MR::DWI; using namespace MR::DWI::Tractography; using namespace MR::DWI::Tractography::Connectome; +const char *file_outputs[] = {"per_edge", "per_node", "single", NULL}; -const char* file_outputs[] = { "per_edge", "per_node", "single", NULL }; +const OptionGroup TrackOutputOptions = + OptionGroup("Options for determining the content / format of output files") + + + Option( + "nodes", + "only select tracks that involve a set of nodes of interest (provide as a comma-separated list of integers)") + + Argument("list").type_sequence_int() -const OptionGroup TrackOutputOptions = OptionGroup ("Options for determining the content / format of output files") + + Option("exclusive", "only select tracks that exclusively connect nodes from within the list of nodes of interest") - + Option ("nodes", "only select tracks that involve a set of nodes of interest (provide as a comma-separated list of integers)") - + Argument ("list").type_sequence_int() + + Option("files", + "select how the resulting streamlines will be grouped in output files. " + "Options are: per_edge, per_node, single (default: per_edge)") + + Argument("option").type_choice(file_outputs) - + Option ("exclusive", "only select tracks that exclusively connect nodes from within the list of nodes of interest") + + Option("exemplars", + "generate a mean connection exemplar per edge, rather than keeping all streamlines " + "(the parcellation node image must be provided in order to constrain the exemplar endpoints)") + + Argument("image").type_image_in() - + Option ("files", "select how the resulting streamlines will be grouped in output files. " - "Options are: per_edge, per_node, single (default: per_edge)") - + Argument ("option").type_choice (file_outputs) + + + Option("keep_unassigned", + "by default, the program discards those streamlines that are not successfully assigned to a node. " + "Set this option to generate corresponding outputs containing these streamlines (labelled as node index 0)") - + Option ("exemplars", "generate a mean connection exemplar per edge, rather than keeping all streamlines " - "(the parcellation node image must be provided in order to constrain the exemplar endpoints)") - + Argument ("image").type_image_in() + + Option("keep_self", + "by default, the program will not output streamlines that connect to the same node at both ends. " + "Set this option to instead keep these self-connections."); - + Option ("keep_unassigned", "by default, the program discards those streamlines that are not successfully assigned to a node. " - "Set this option to generate corresponding outputs containing these streamlines (labelled as node index 0)") - - + Option ("keep_self", "by default, the program will not output streamlines that connect to the same node at both ends. " - "Set this option to instead keep these self-connections."); - - - - -void usage () -{ +void usage() { // Note: Creation of this OptionGroup depends on Tractography::TrackWeightsInOption // already being defined; therefore, it cannot be defined statically, and // must be constructed after the command is executed. - const OptionGroup TrackWeightsOptions = OptionGroup ("Options for importing / exporting streamline weights") - + Tractography::TrackWeightsInOption - + Option ("prefix_tck_weights_out", "provide a prefix for outputting a text file corresponding to each output file, " - "each containing only the streamline weights relevant for that track file") - + Argument ("prefix").type_text(); - - + const OptionGroup TrackWeightsOptions = + OptionGroup("Options for importing / exporting streamline weights") + Tractography::TrackWeightsInOption + + Option("prefix_tck_weights_out", + "provide a prefix for outputting a text file corresponding to each output file, " + "each containing only the streamline weights relevant for that track file") + + Argument("prefix").type_text(); AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Extract streamlines from a tractogram based on their assignment to parcellated nodes"; DESCRIPTION - + "The compulsory input file \"assignments_in\" should contain a text file where there is one row for each streamline, " - "and each row contains a list of numbers corresponding to the parcels to which that streamline was assigned " - "(most typically there will be two entries per streamline, one for each endpoint; but this is not strictly a requirement). " - "This file will most typically be generated using the tck2connectome command with the -out_assignments option."; + +"The compulsory input file \"assignments_in\" should contain a text file where there is one row for each " + "streamline, " + "and each row contains a list of numbers corresponding to the parcels to which that streamline was assigned " + "(most typically there will be two entries per streamline, one for each endpoint; but this is not strictly a " + "requirement). " + "This file will most typically be generated using the tck2connectome command with the -out_assignments option."; EXAMPLES - + Example ("Default usage", - "connectome2tck tracks.tck assignments.txt edge-", - "The command will generate one track file for every edge in the connectome, with the name of each file " - "indicating the nodes connected via that edge; for instance, all streamlines connecting nodes " - "23 and 49 will be written to file \"edge-23-49.tck\".") - - + Example ("Extract only the streamlines between nodes 1 and 2", - "connectome2tck tracks.tck assignments.txt tracks_1_2.tck -nodes 1,2 -exclusive -files single", - "Since only a single edge is of interest, this example provides only the two nodes involved " - "in that edge to the -nodes option, adds the -exclusive option so that only streamlines for which " - "both assigned nodes are in the list of nodes of interest are extracted (i.e. only streamlines connecting " - "nodes 1 and 2 in this example), and writes the result to a single output track file.") - - + Example ("Extract the streamlines connecting node 15 to all other nodes in the parcellation, with one track file for each edge", - "connectome2tck tracks.tck assignments.txt from_15_to_ -nodes 15 -keep_self", - "The command will generate the same number of track files as there are nodes in the parcellation: " - "one each for the streamlines connecting node 15 to every other node; i.e. " - "\"from_15_to_1.tck\", \"from_15_to_2.tck\", \"from_15_to_3.tck\", etc.. " - "Because the -keep_self option is specified, file \"from_15_to_15.tck\" will also be " - "generated, containing those streamlines that connect to node 15 at both endpoints.") - - + Example ("For every node, generate a file containing all streamlines connected to that node", - "connectome2tck tracks.tck assignments.txt node -files per_node", - "Here the command will generate one track file for every node in the connectome: " - "\"node1.tck\", \"node2.tck\", \"node3.tck\", etc.. Each of these files will contain " - "all streamlines that connect the node of that index to another node in the connectome " - "(it does not select all tracks connecting a particular node, since the -keep_self " - "option was omitted and therefore e.g. a streamline that is assigned to node 41 will " - "not be present in file \"node41.tck\"). Each streamline in the input tractogram will " - "in fact appear in two different output track files; e.g. a streamline connecting nodes " - "8 and 56 will be present both in file \"node8.tck\" and file \"node56.tck\".") - - + Example ("Get all streamlines that were not successfully assigned to a node pair", - "connectome2tck tracks.tck assignments.txt unassigned.tck -nodes 0 -keep_self -files single", - "Node index 0 corresponds to streamline endpoints that were not successfully assigned to " - "a node. As such, by selecting all streamlines that are assigned to \"node 0\" " - "(including those streamlines for which neither endpoint is assigned to a node due to " - "use of the -keep_self option), the single output track file will contain all streamlines " - "for which at least one of the two endpoints was not successfully assigned to a node.") - - + Example ("Generate a single track file containing edge exemplar trajectories", - "connectome2tck tracks.tck assignments.txt exemplars.tck -files single -exemplars nodes.mif", - "This produces the track file that is required as input when attempting to display " - "connectome edges using the streamlines or streamtubes geometries within the meview " - "connectome tool."); + +Example("Default usage", + "connectome2tck tracks.tck assignments.txt edge-", + "The command will generate one track file for every edge in the connectome, with the name of each file " + "indicating the nodes connected via that edge; for instance, all streamlines connecting nodes " + "23 and 49 will be written to file \"edge-23-49.tck\".") + + + Example( + "Extract only the streamlines between nodes 1 and 2", + "connectome2tck tracks.tck assignments.txt tracks_1_2.tck -nodes 1,2 -exclusive -files single", + "Since only a single edge is of interest, this example provides only the two nodes involved " + "in that edge to the -nodes option, adds the -exclusive option so that only streamlines for which " + "both assigned nodes are in the list of nodes of interest are extracted (i.e. only streamlines connecting " + "nodes 1 and 2 in this example), and writes the result to a single output track file.") + + + Example("Extract the streamlines connecting node 15 to all other nodes in the parcellation, with one track " + "file for each edge", + "connectome2tck tracks.tck assignments.txt from_15_to_ -nodes 15 -keep_self", + "The command will generate the same number of track files as there are nodes in the parcellation: " + "one each for the streamlines connecting node 15 to every other node; i.e. " + "\"from_15_to_1.tck\", \"from_15_to_2.tck\", \"from_15_to_3.tck\", etc.. " + "Because the -keep_self option is specified, file \"from_15_to_15.tck\" will also be " + "generated, containing those streamlines that connect to node 15 at both endpoints.") + + + Example("For every node, generate a file containing all streamlines connected to that node", + "connectome2tck tracks.tck assignments.txt node -files per_node", + "Here the command will generate one track file for every node in the connectome: " + "\"node1.tck\", \"node2.tck\", \"node3.tck\", etc.. Each of these files will contain " + "all streamlines that connect the node of that index to another node in the connectome " + "(it does not select all tracks connecting a particular node, since the -keep_self " + "option was omitted and therefore e.g. a streamline that is assigned to node 41 will " + "not be present in file \"node41.tck\"). Each streamline in the input tractogram will " + "in fact appear in two different output track files; e.g. a streamline connecting nodes " + "8 and 56 will be present both in file \"node8.tck\" and file \"node56.tck\".") + + + Example("Get all streamlines that were not successfully assigned to a node pair", + "connectome2tck tracks.tck assignments.txt unassigned.tck -nodes 0 -keep_self -files single", + "Node index 0 corresponds to streamline endpoints that were not successfully assigned to " + "a node. As such, by selecting all streamlines that are assigned to \"node 0\" " + "(including those streamlines for which neither endpoint is assigned to a node due to " + "use of the -keep_self option), the single output track file will contain all streamlines " + "for which at least one of the two endpoints was not successfully assigned to a node.") + + + Example("Generate a single track file containing edge exemplar trajectories", + "connectome2tck tracks.tck assignments.txt exemplars.tck -files single -exemplars nodes.mif", + "This produces the track file that is required as input when attempting to display " + "connectome edges using the streamlines or streamtubes geometries within the meview " + "connectome tool."); ARGUMENTS - + Argument ("tracks_in", "the input track file").type_file_in() - + Argument ("assignments_in", "input text file containing the node assignments for each streamline").type_file_in() - + Argument ("prefix_out", "the output file / prefix").type_text(); - + +Argument("tracks_in", "the input track file").type_file_in() + + Argument("assignments_in", "input text file containing the node assignments for each streamline").type_file_in() + + Argument("prefix_out", "the output file / prefix").type_text(); OPTIONS - + TrackOutputOptions - + TrackWeightsOptions; - + +TrackOutputOptions + TrackWeightsOptions; } - - - -void run () -{ +void run() { Tractography::Properties properties; - Tractography::Reader reader (argument[0], properties); + Tractography::Reader reader(argument[0], properties); - vector< vector > assignments_lists; - assignments_lists.reserve (to(properties["count"])); + vector> assignments_lists; + assignments_lists.reserve(to(properties["count"])); vector assignments_pairs; bool nonpair_found = false; node_t max_node_index = 0; { - std::ifstream stream (argument[1]); + std::ifstream stream(argument[1]); std::string line; - ProgressBar progress ("reading streamline assignments file"); - while (std::getline (stream, line)) { - line = strip (line.substr (0, line.find_first_of ('#'))); + ProgressBar progress("reading streamline assignments file"); + while (std::getline(stream, line)) { + line = strip(line.substr(0, line.find_first_of('#'))); if (line.empty()) continue; - std::stringstream line_stream (line); + std::stringstream line_stream(line); vector nodes; while (1) { node_t n; line_stream >> n; - if (!line_stream) break; - nodes.push_back (n); - max_node_index = std::max (max_node_index, n); + if (!line_stream) + break; + nodes.push_back(n); + max_node_index = std::max(max_node_index, n); } if (nodes.size() != 2) nonpair_found = true; - assignments_lists.push_back (std::move (nodes)); + assignments_lists.push_back(std::move(nodes)); ++progress; } } const size_t count = to(properties["count"]); if (assignments_lists.size() != count) - throw Exception ("Assignments file contains " + str(assignments_lists.size()) + " entries; track file contains " + str(count) + " tracks"); + throw Exception("Assignments file contains " + str(assignments_lists.size()) + " entries; track file contains " + + str(count) + " tracks"); // If the node assignments have been performed in such a way that each streamline is // assigned to precisely two nodes, use the assignments_pairs class which is @@ -200,102 +199,128 @@ void run () // where each streamline could potentially be assigned to any number of nodes is // now supported. if (!nonpair_found) { - INFO ("Assignments file contains node pair for every streamline; operating accordingly"); - assignments_pairs.reserve (assignments_lists.size()); + INFO("Assignments file contains node pair for every streamline; operating accordingly"); + assignments_pairs.reserve(assignments_lists.size()); for (auto i = assignments_lists.begin(); i != assignments_lists.end(); ++i) - assignments_pairs.push_back (NodePair ((*i)[0], (*i)[1])); + assignments_pairs.push_back(NodePair((*i)[0], (*i)[1])); assignments_lists.clear(); } - const std::string prefix (argument[2]); - auto opt = get_options ("prefix_tck_weights_out"); - const std::string weights_prefix = opt.size() ? std::string (opt[0][0]) : ""; + const std::string prefix(argument[2]); + auto opt = get_options("prefix_tck_weights_out"); + const std::string weights_prefix = opt.size() ? std::string(opt[0][0]) : ""; - INFO ("Maximum node index in assignments file is " + str(max_node_index)); + INFO("Maximum node index in assignments file is " + str(max_node_index)); - const node_t first_node = get_options ("keep_unassigned").size() ? 0 : 1; - const bool keep_self = get_options ("keep_self").size(); + const node_t first_node = get_options("keep_unassigned").size() ? 0 : 1; + const bool keep_self = get_options("keep_self").size(); // Get the list of nodes of interest vector nodes; - opt = get_options ("nodes"); + opt = get_options("nodes"); bool manual_node_list = false; if (opt.size()) { manual_node_list = true; - const auto data = parse_ints (opt[0][0]); + const auto data = parse_ints(opt[0][0]); bool zero_in_list = false; for (auto i : data) { if (i > max_node_index) { - WARN ("Node of interest " + str(i) + " is above the maximum detected node index of " + str(max_node_index)); + WARN("Node of interest " + str(i) + " is above the maximum detected node index of " + str(max_node_index)); } else { - nodes.push_back (i); + nodes.push_back(i); if (!i) zero_in_list = true; } } if (!zero_in_list && !first_node) - nodes.push_back (0); - std::sort (nodes.begin(), nodes.end()); + nodes.push_back(0); + std::sort(nodes.begin(), nodes.end()); } else { for (node_t i = first_node; i <= max_node_index; ++i) - nodes.push_back (i); + nodes.push_back(i); } - const bool exclusive = get_options ("exclusive").size(); + const bool exclusive = get_options("exclusive").size(); if (exclusive && !manual_node_list) - WARN ("List of nodes of interest not provided; -exclusive option will have no effect"); + WARN("List of nodes of interest not provided; -exclusive option will have no effect"); - opt = get_options ("files"); + opt = get_options("files"); const int file_format = opt.size() ? opt[0][0] : 0; - opt = get_options ("exemplars"); + opt = get_options("exemplars"); if (opt.size()) { if (keep_self) - WARN ("Exemplars cannot be calculated for node self-connections; -keep_self option ignored"); + WARN("Exemplars cannot be calculated for node self-connections; -keep_self option ignored"); // Load the node image, get the centres of mass // Generate exemplars - these can _only_ be done per edge, and requires a mutex per edge to multi-thread - auto image = Image::open (opt[0][0]); - vector COMs (max_node_index+1, Eigen::Vector3f (0.0f, 0.0f, 0.0f)); - vector volumes (max_node_index+1, 0); - for (auto i = Loop() (image); i; ++i) { + auto image = Image::open(opt[0][0]); + vector COMs(max_node_index + 1, Eigen::Vector3f(0.0f, 0.0f, 0.0f)); + vector volumes(max_node_index + 1, 0); + for (auto i = Loop()(image); i; ++i) { const node_t index = image.value(); if (index) { while (index >= COMs.size()) { - COMs.push_back (Eigen::Vector3f (0.0f, 0.0f, 0.0f)); - volumes.push_back (0); + COMs.push_back(Eigen::Vector3f(0.0f, 0.0f, 0.0f)); + volumes.push_back(0); } - COMs[index] += Eigen::Vector3f (image.index(0), image.index(1), image.index(2)); + COMs[index] += Eigen::Vector3f(image.index(0), image.index(1), image.index(2)); ++volumes[index]; } } if (COMs.size() > max_node_index + 1) { - WARN ("Parcellation image \"" + std::string (opt[0][0]) + "\" provided via -exemplars option contains more nodes (" + str(COMs.size()-1) + ") than are present in input assignments file \"" + std::string (argument[1]) + "\" (" + str(max_node_index) + ")"); - max_node_index = COMs.size()-1; + WARN("Parcellation image \"" + std::string(opt[0][0]) + + "\" provided via -exemplars option contains more nodes (" + str(COMs.size() - 1) + + ") than are present in input assignments file \"" + std::string(argument[1]) + "\" (" + str(max_node_index) + + ")"); + max_node_index = COMs.size() - 1; } - Transform transform (image); + Transform transform(image); for (node_t index = 1; index <= max_node_index; ++index) { if (volumes[index]) - COMs[index] = (transform.voxel2scanner * (COMs[index] * (1.0f / float(volumes[index]))).cast()).cast(); + COMs[index] = (transform.voxel2scanner * (COMs[index] * (1.0f / float(volumes[index]))).cast()) + .cast(); else COMs[index][0] = COMs[index][1] = COMs[index][2] = NaN; } // If user specifies a subset of nodes, only a subset of exemplars need to be calculated - WriterExemplars generator (properties, nodes, exclusive, first_node, COMs); + WriterExemplars generator(properties, nodes, exclusive, first_node, COMs); { std::mutex mutex; - ProgressBar progress ("generating exemplars for connectome", count); + ProgressBar progress("generating exemplars for connectome", count); if (assignments_pairs.size()) { - auto loader = [&] (Tractography::Connectome::Streamline_nodepair& out) { if (!reader (out)) return false; out.set_nodes (assignments_pairs[out.get_index()]); return true; }; - auto worker = [&] (const Tractography::Connectome::Streamline_nodepair& in) { generator (in); std::lock_guard lock (mutex); ++progress; return true; }; - Thread::run_queue (loader, Thread::batch (Tractography::Connectome::Streamline_nodepair()), Thread::multi (worker)); + auto loader = [&](Tractography::Connectome::Streamline_nodepair &out) { + if (!reader(out)) + return false; + out.set_nodes(assignments_pairs[out.get_index()]); + return true; + }; + auto worker = [&](const Tractography::Connectome::Streamline_nodepair &in) { + generator(in); + std::lock_guard lock(mutex); + ++progress; + return true; + }; + Thread::run_queue( + loader, Thread::batch(Tractography::Connectome::Streamline_nodepair()), Thread::multi(worker)); } else { - auto loader = [&] (Tractography::Connectome::Streamline_nodelist& out) { if (!reader (out)) return false; out.set_nodes (assignments_lists[out.get_index()]); return true; }; - auto worker = [&] (const Tractography::Connectome::Streamline_nodelist& in) { generator (in); std::lock_guard lock (mutex); ++progress; return true; }; - Thread::run_queue (loader, Thread::batch (Tractography::Connectome::Streamline_nodelist()), Thread::multi (worker)); + auto loader = [&](Tractography::Connectome::Streamline_nodelist &out) { + if (!reader(out)) + return false; + out.set_nodes(assignments_lists[out.get_index()]); + return true; + }; + auto worker = [&](const Tractography::Connectome::Streamline_nodelist &in) { + generator(in); + std::lock_guard lock(mutex); + ++progress; + return true; + }; + Thread::run_queue( + loader, Thread::batch(Tractography::Connectome::Streamline_nodelist()), Thread::multi(worker)); } } @@ -304,95 +329,106 @@ void run () // Get exemplars to the output file(s), depending on the requested format if (file_format == 0) { // One file per edge if (exclusive) { - ProgressBar progress ("writing exemplars to files", nodes.size() * (nodes.size()-1) / 2); + ProgressBar progress("writing exemplars to files", nodes.size() * (nodes.size() - 1) / 2); for (size_t i = 0; i != nodes.size(); ++i) { const node_t one = nodes[i]; - for (size_t j = i+1; j != nodes.size(); ++j) { + for (size_t j = i + 1; j != nodes.size(); ++j) { const node_t two = nodes[j]; - generator.write (one, two, prefix + str(one) + "-" + str(two) + ".tck", weights_prefix.size() ? (weights_prefix + str(one) + "-" + str(two) + ".csv") : ""); + generator.write(one, + two, + prefix + str(one) + "-" + str(two) + ".tck", + weights_prefix.size() ? (weights_prefix + str(one) + "-" + str(two) + ".csv") : ""); ++progress; } } } else { // For each node in the list, write one file for an exemplar to every other node - ProgressBar progress ("writing exemplars to files", nodes.size() * COMs.size()); + ProgressBar progress("writing exemplars to files", nodes.size() * COMs.size()); for (vector::const_iterator n = nodes.begin(); n != nodes.end(); ++n) { for (size_t i = first_node; i != COMs.size(); ++i) { - generator.write (*n, i, prefix + str(*n) + "-" + str(i) + ".tck", weights_prefix.size() ? (weights_prefix + str(*n) + "-" + str(i) + ".csv") : ""); + generator.write(*n, + i, + prefix + str(*n) + "-" + str(i) + ".tck", + weights_prefix.size() ? (weights_prefix + str(*n) + "-" + str(i) + ".csv") : ""); ++progress; } } } } else if (file_format == 1) { // One file per node - ProgressBar progress ("writing exemplars to files", nodes.size()); + ProgressBar progress("writing exemplars to files", nodes.size()); for (vector::const_iterator n = nodes.begin(); n != nodes.end(); ++n) { - generator.write (*n, prefix + str(*n) + ".tck", weights_prefix.size() ? (weights_prefix + str(*n) + ".csv") : ""); + generator.write( + *n, prefix + str(*n) + ".tck", weights_prefix.size() ? (weights_prefix + str(*n) + ".csv") : ""); ++progress; } } else if (file_format == 2) { // Single file std::string path = prefix; - if (path.rfind (".tck") != path.size() - 4) + if (path.rfind(".tck") != path.size() - 4) path += ".tck"; std::string weights_path = weights_prefix; - if (weights_prefix.size() && weights_path.rfind (".tck") != weights_path.size() - 4) + if (weights_prefix.size() && weights_path.rfind(".tck") != weights_path.size() - 4) weights_path += ".csv"; - generator.write (path, weights_path); + generator.write(path, weights_path); } } else { // Old behaviour ie. all tracks, rather than generating exemplars - WriterExtraction writer (properties, nodes, exclusive, keep_self); + WriterExtraction writer(properties, nodes, exclusive, keep_self); switch (file_format) { - case 0: // One file per edge - for (size_t i = 0; i != nodes.size(); ++i) { - const node_t one = nodes[i]; - if (exclusive) { - for (size_t j = i; j != nodes.size(); ++j) { - const node_t two = nodes[j]; - writer.add (one, two, prefix + str(one) + "-" + str(two) + ".tck", weights_prefix.size() ? (weights_prefix + str(one) + "-" + str(two) + ".csv") : ""); - } - } else { - // Allow duplication of edges; want to have an exhaustive set of files for each node - for (node_t two = first_node; two <= max_node_index; ++two) - writer.add (one, two, prefix + str(one) + "-" + str(two) + ".tck", weights_prefix.size() ? (weights_prefix + str(one) + "-" + str(two) + ".csv") : ""); + case 0: // One file per edge + for (size_t i = 0; i != nodes.size(); ++i) { + const node_t one = nodes[i]; + if (exclusive) { + for (size_t j = i; j != nodes.size(); ++j) { + const node_t two = nodes[j]; + writer.add(one, + two, + prefix + str(one) + "-" + str(two) + ".tck", + weights_prefix.size() ? (weights_prefix + str(one) + "-" + str(two) + ".csv") : ""); } + } else { + // Allow duplication of edges; want to have an exhaustive set of files for each node + for (node_t two = first_node; two <= max_node_index; ++two) + writer.add(one, + two, + prefix + str(one) + "-" + str(two) + ".tck", + weights_prefix.size() ? (weights_prefix + str(one) + "-" + str(two) + ".csv") : ""); } - INFO ("A total of " + str (writer.file_count()) + " output track files will be generated (one for each edge)"); - break; - case 1: // One file per node - for (vector::const_iterator i = nodes.begin(); i != nodes.end(); ++i) - writer.add (*i, prefix + str(*i) + ".tck", weights_prefix.size() ? (weights_prefix + str(*i) + ".csv") : ""); - INFO ("A total of " + str (writer.file_count()) + " output track files will be generated (one for each node)"); - break; - case 2: // Single file - std::string path = prefix; - if (path.rfind (".tck") != path.size() - 4) - path += ".tck"; - std::string weights_path = weights_prefix; - if (weights_prefix.size() && weights_path.rfind (".tck") != weights_path.size() - 4) - weights_path += ".csv"; - writer.add (nodes, path, weights_path); - break; + } + INFO("A total of " + str(writer.file_count()) + " output track files will be generated (one for each edge)"); + break; + case 1: // One file per node + for (vector::const_iterator i = nodes.begin(); i != nodes.end(); ++i) + writer.add(*i, prefix + str(*i) + ".tck", weights_prefix.size() ? (weights_prefix + str(*i) + ".csv") : ""); + INFO("A total of " + str(writer.file_count()) + " output track files will be generated (one for each node)"); + break; + case 2: // Single file + std::string path = prefix; + if (path.rfind(".tck") != path.size() - 4) + path += ".tck"; + std::string weights_path = weights_prefix; + if (weights_prefix.size() && weights_path.rfind(".tck") != weights_path.size() - 4) + weights_path += ".csv"; + writer.add(nodes, path, weights_path); + break; } - ProgressBar progress ("Extracting tracks from connectome", count); + ProgressBar progress("Extracting tracks from connectome", count); if (assignments_pairs.size()) { Tractography::Connectome::Streamline_nodepair tck; - while (reader (tck)) { - tck.set_nodes (assignments_pairs[tck.get_index()]); - writer (tck); + while (reader(tck)) { + tck.set_nodes(assignments_pairs[tck.get_index()]); + writer(tck); ++progress; } } else { Tractography::Connectome::Streamline_nodelist tck; - while (reader (tck)) { - tck.set_nodes (assignments_lists[tck.get_index()]); - writer (tck); + while (reader(tck)) { + tck.set_nodes(assignments_lists[tck.get_index()]); + writer(tck); ++progress; } } - } - } diff --git a/cmd/connectomeedit.cpp b/cmd/connectomeedit.cpp index d7503eebef..bdb5f9e759 100644 --- a/cmd/connectomeedit.cpp +++ b/cmd/connectomeedit.cpp @@ -15,58 +15,57 @@ */ #include "command.h" -#include "file/matrix.h" #include "connectome/enhance.h" +#include "file/matrix.h" using namespace MR; using namespace MR::Connectome; using namespace MR::Math; using namespace App; +const char *operations[] = {"to_symmetric", "upper_triangular", "lower_triangular", "transpose", "zero_diagonal", NULL}; -const char* operations[] = { - "to_symmetric", - "upper_triangular", - "lower_triangular", - "transpose", - "zero_diagonal", - NULL -}; - - -void usage () -{ +void usage() { AUTHOR = "Matteo Frigo (matteo.frigo@inria.fr)"; SYNOPSIS = "Perform basic operations on a connectome"; ARGUMENTS - + Argument ("input", "the input connectome.").type_text () + +Argument("input", "the input connectome.").type_text() - + Argument ("operation", "the operation to apply, one of: " + join(operations, ", ") + ".").type_choice (operations) + + Argument("operation", "the operation to apply, one of: " + join(operations, ", ") + ".").type_choice(operations) - + Argument ("output", "the output connectome.").type_text(); + + Argument("output", "the output connectome.").type_text(); } - - -void run () -{ - MR::Connectome::matrix_type connectome = File::Matrix::load_matrix (argument[0]); +void run() { + MR::Connectome::matrix_type connectome = File::Matrix::load_matrix(argument[0]); MR::Connectome::check(connectome); const int op = argument[1]; - const std::string& output_path = argument[2]; + const std::string &output_path = argument[2]; INFO("Applying \'" + str(operations[op]) + "\' transformation to the input connectome."); switch (op) { - case 0: MR::Connectome::to_symmetric (connectome); break; - case 1: MR::Connectome::to_upper (connectome); break; - case 2: MR::Connectome::to_upper (connectome); connectome.transposeInPlace(); break; - case 3: connectome.transposeInPlace(); break; - case 4: connectome.matrix().diagonal().setZero(); break; - default: assert (0); - } + case 0: + MR::Connectome::to_symmetric(connectome); + break; + case 1: + MR::Connectome::to_upper(connectome); + break; + case 2: + MR::Connectome::to_upper(connectome); + connectome.transposeInPlace(); + break; + case 3: + connectome.transposeInPlace(); + break; + case 4: + connectome.matrix().diagonal().setZero(); + break; + default: + assert(0); + } File::Matrix::save_matrix(connectome, output_path); } \ No newline at end of file diff --git a/cmd/connectomestats.cpp b/cmd/connectomestats.cpp index a1e97b5030..ab81f9eaa0 100644 --- a/cmd/connectomestats.cpp +++ b/cmd/connectomestats.cpp @@ -31,7 +31,6 @@ #include "stats/permtest.h" - using namespace MR; using namespace App; using namespace MR::Math::Stats; @@ -42,10 +41,7 @@ using Math::Stats::matrix_type; using Math::Stats::vector_type; using Stats::PermTest::count_matrix_type; - -const char* algorithms[] = { "nbs", "tfnbs", "none", nullptr }; - - +const char *algorithms[] = {"nbs", "tfnbs", "none", nullptr}; // TODO Eventually these will move to some kind of TFCE header #define TFCE_DH_DEFAULT 0.1 @@ -54,210 +50,200 @@ const char* algorithms[] = { "nbs", "tfnbs", "none", nullptr }; #define EMPIRICAL_SKEW_DEFAULT 1.0 - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Connectome group-wise statistics at the edge level using non-parametric permutation testing"; DESCRIPTION - + "For the TFNBS algorithm, default parameters for statistical enhancement " - "have been set based on the work in: \n" - "Vinokur, L.; Zalesky, A.; Raffelt, D.; Smith, R.E. & Connelly, A. A Novel Threshold-Free Network-Based Statistics Method: Demonstration using Simulated Pathology. OHBM, 2015, 4144; \n" - "and: \n" - "Vinokur, L.; Zalesky, A.; Raffelt, D.; Smith, R.E. & Connelly, A. A novel threshold-free network-based statistical method: Demonstration and parameter optimisation using in vivo simulated pathology. In Proc ISMRM, 2015, 2846. \n" - "Note however that not only was the optimisation of these parameters not " - "very precise, but the outcomes of statistical inference (for both this " - "algorithm and the NBS method) can vary markedly for even small changes to " - "enhancement parameters. Therefore the specificity of results obtained using " - "either of these methods should be interpreted with caution." - + Math::Stats::GLM::column_ones_description; - + +"For the TFNBS algorithm, default parameters for statistical enhancement " + "have been set based on the work in: \n" + "Vinokur, L.; Zalesky, A.; Raffelt, D.; Smith, R.E. & Connelly, A. A Novel Threshold-Free Network-Based Statistics " + "Method: Demonstration using Simulated Pathology. OHBM, 2015, 4144; \n" + "and: \n" + "Vinokur, L.; Zalesky, A.; Raffelt, D.; Smith, R.E. & Connelly, A. A novel threshold-free network-based statistical " + "method: Demonstration and parameter optimisation using in vivo simulated pathology. In Proc ISMRM, 2015, 2846. \n" + "Note however that not only was the optimisation of these parameters not " + "very precise, but the outcomes of statistical inference (for both this " + "algorithm and the NBS method) can vary markedly for even small changes to " + "enhancement parameters. Therefore the specificity of results obtained using " + "either of these methods should be interpreted with caution." + + Math::Stats::GLM::column_ones_description; ARGUMENTS - + Argument ("input", "a text file listing the file names of the input connectomes").type_file_in () + +Argument("input", "a text file listing the file names of the input connectomes").type_file_in() - + Argument ("algorithm", "the algorithm to use in network-based clustering/enhancement. " - "Options are: " + join(algorithms, ", ")).type_choice (algorithms) + + Argument("algorithm", + "the algorithm to use in network-based clustering/enhancement. " + "Options are: " + + join(algorithms, ", ")) + .type_choice(algorithms) - + Argument ("design", "the design matrix").type_file_in () + + Argument("design", "the design matrix").type_file_in() - + Argument ("contrast", "the contrast matrix").type_file_in () - - + Argument ("output", "the filename prefix for all output.").type_text(); + + Argument("contrast", "the contrast matrix").type_file_in() + + Argument("output", "the filename prefix for all output.").type_text(); OPTIONS - + Math::Stats::shuffle_options (true, EMPIRICAL_SKEW_DEFAULT) - - // TODO OptionGroup these, and provide a generic loader function - + Stats::TFCE::Options (TFCE_DH_DEFAULT, TFCE_E_DEFAULT, TFCE_H_DEFAULT) + +Math::Stats::shuffle_options(true, EMPIRICAL_SKEW_DEFAULT) - + Math::Stats::GLM::glm_options ("edge") + // TODO OptionGroup these, and provide a generic loader function + + Stats::TFCE::Options(TFCE_DH_DEFAULT, TFCE_E_DEFAULT, TFCE_H_DEFAULT) - + OptionGroup ("Additional options for connectomestats") + + Math::Stats::GLM::glm_options("edge") - + Option ("threshold", "the t-statistic value to use in threshold-based clustering algorithms") - + Argument ("value").type_float (0.0); + + OptionGroup("Additional options for connectomestats") - REFERENCES + "* If using the NBS algorithm: \n" - "Zalesky, A.; Fornito, A. & Bullmore, E. T. Network-based statistic: Identifying differences in brain networks. \n" - "NeuroImage, 2010, 53, 1197-1207" + + Option("threshold", "the t-statistic value to use in threshold-based clustering algorithms") + + Argument("value").type_float(0.0); - + "* If using the TFNBS algorithm: \n" - "Baggio, H.C.; Abos, A.; Segura, B.; Campabadal, A.; Garcia-Diaz, A.; Uribe, C.; Compta, Y.; Marti, M.J.; Valldeoriola, F.; Junque, C. Statistical inference in brain graphs using threshold-free network-based statistics." - "HBM, 2018, 39, 2289-2302" + REFERENCES + + "* If using the NBS algorithm: \n" + "Zalesky, A.; Fornito, A. & Bullmore, E. T. Network-based statistic: Identifying differences in brain networks. " + "\n" + "NeuroImage, 2010, 53, 1197-1207" - + "* If using the -nonstationary option: \n" - "Salimi-Khorshidi, G.; Smith, S.M. & Nichols, T.E. Adjusting the effect of nonstationarity in cluster-based and TFCE inference. \n" - "Neuroimage, 2011, 54(3), 2006-19"; + + "* If using the TFNBS algorithm: \n" + "Baggio, H.C.; Abos, A.; Segura, B.; Campabadal, A.; Garcia-Diaz, A.; Uribe, C.; Compta, Y.; Marti, M.J.; " + "Valldeoriola, F.; Junque, C. Statistical inference in brain graphs using threshold-free network-based " + "statistics." + "HBM, 2018, 39, 2289-2302" + + "* If using the -nonstationary option: \n" + "Salimi-Khorshidi, G.; Smith, S.M. & Nichols, T.E. Adjusting the effect of nonstationarity in cluster-based " + "and TFCE inference. \n" + "Neuroimage, 2011, 54(3), 2006-19"; } - - - -void load_tfce_parameters (Stats::TFCE::Wrapper& enhancer) -{ - const default_type dH = get_option_value ("tfce_dh", TFCE_DH_DEFAULT); - const default_type E = get_option_value ("tfce_e", TFCE_E_DEFAULT); - const default_type H = get_option_value ("tfce_h", TFCE_H_DEFAULT); - enhancer.set_tfce_parameters (dH, E, H); +void load_tfce_parameters(Stats::TFCE::Wrapper &enhancer) { + const default_type dH = get_option_value("tfce_dh", TFCE_DH_DEFAULT); + const default_type E = get_option_value("tfce_e", TFCE_E_DEFAULT); + const default_type H = get_option_value("tfce_h", TFCE_H_DEFAULT); + enhancer.set_tfce_parameters(dH, E, H); } - - // Define data importer class that will obtain connectome data for a // specific subject based on the string path to the image file for // that subject -class SubjectConnectomeImport : public SubjectDataImportBase -{ - public: - SubjectConnectomeImport (const std::string& path) : - SubjectDataImportBase (path) - { - auto M = File::Matrix::load_matrix (path); - Connectome::check (M); - if (Connectome::is_directed (M)) - throw Exception ("Connectome from file \"" + Path::basename (path) + "\" is a directed matrix"); - Connectome::to_upper (M); - Connectome::Mat2Vec mat2vec (M.rows()); - mat2vec.M2V (M, data); - } - - void operator() (matrix_type::RowXpr row) const override - { - assert (row.size() == data.size()); - row = data; - } +class SubjectConnectomeImport : public SubjectDataImportBase { +public: + SubjectConnectomeImport(const std::string &path) : SubjectDataImportBase(path) { + auto M = File::Matrix::load_matrix(path); + Connectome::check(M); + if (Connectome::is_directed(M)) + throw Exception("Connectome from file \"" + Path::basename(path) + "\" is a directed matrix"); + Connectome::to_upper(M); + Connectome::Mat2Vec mat2vec(M.rows()); + mat2vec.M2V(M, data); + } - default_type operator[] (const index_type index) const override - { - assert (index < index_type(data.size())); - return (data[index]); - } + void operator()(matrix_type::RowXpr row) const override { + assert(row.size() == data.size()); + row = data; + } - index_type size() const override { return data.size(); } + default_type operator[](const index_type index) const override { + assert(index < index_type(data.size())); + return (data[index]); + } - private: - vector_type data; + index_type size() const override { return data.size(); } +private: + vector_type data; }; - - -void run() -{ +void run() { // Read file names and check files exist CohortDataImport importer; - importer.initialise (argument[0]); - CONSOLE ("Number of inputs: " + str(importer.size())); + importer.initialise(argument[0]); + CONSOLE("Number of inputs: " + str(importer.size())); const index_type num_edges = importer[0]->size(); for (index_type i = 1; i < importer.size(); ++i) { if (importer[i]->size() != importer[0]->size()) - throw Exception ("Size of connectome for subject " + str(i) + " (file \"" + importer[i]->name() + "\" does not match that of first subject"); + throw Exception("Size of connectome for subject " + str(i) + " (file \"" + importer[i]->name() + + "\" does not match that of first subject"); } // TODO Could determine this from the vector length with the right equation - const MR::Connectome::matrix_type example_connectome = File::Matrix::load_matrix (importer[0]->name()); + const MR::Connectome::matrix_type example_connectome = File::Matrix::load_matrix(importer[0]->name()); const MR::Connectome::node_t num_nodes = example_connectome.rows(); - Connectome::Mat2Vec mat2vec (num_nodes); + Connectome::Mat2Vec mat2vec(num_nodes); // Initialise enhancement algorithm std::shared_ptr enhancer; switch (int(argument[1])) { - case 0: { - auto opt = get_options ("threshold"); - if (!opt.size()) - throw Exception ("For NBS algorithm, -threshold option must be provided"); - enhancer.reset (new MR::Connectome::Enhance::NBS (num_nodes, opt[0][0])); - } - break; - case 1: { - std::shared_ptr base (new MR::Connectome::Enhance::NBS (num_nodes)); - enhancer.reset (new Stats::TFCE::Wrapper (base)); - load_tfce_parameters (*(dynamic_cast(enhancer.get()))); - if (get_options ("threshold").size()) - WARN (std::string (argument[1]) + " is a threshold-free algorithm; -threshold option ignored"); - } - break; - case 2: { - enhancer.reset (new MR::Connectome::Enhance::PassThrough()); - if (get_options ("threshold").size()) - WARN ("No enhancement algorithm being used; -threshold option ignored"); - } - break; - default: - throw Exception ("Unknown enhancement algorithm"); + case 0: { + auto opt = get_options("threshold"); + if (!opt.size()) + throw Exception("For NBS algorithm, -threshold option must be provided"); + enhancer.reset(new MR::Connectome::Enhance::NBS(num_nodes, opt[0][0])); + } break; + case 1: { + std::shared_ptr base(new MR::Connectome::Enhance::NBS(num_nodes)); + enhancer.reset(new Stats::TFCE::Wrapper(base)); + load_tfce_parameters(*(dynamic_cast(enhancer.get()))); + if (get_options("threshold").size()) + WARN(std::string(argument[1]) + " is a threshold-free algorithm; -threshold option ignored"); + } break; + case 2: { + enhancer.reset(new MR::Connectome::Enhance::PassThrough()); + if (get_options("threshold").size()) + WARN("No enhancement algorithm being used; -threshold option ignored"); + } break; + default: + throw Exception("Unknown enhancement algorithm"); } - const bool do_nonstationarity_adjustment = get_options ("nonstationarity").size(); - const default_type empirical_skew = get_option_value ("skew_nonstationarity", EMPIRICAL_SKEW_DEFAULT); + const bool do_nonstationarity_adjustment = get_options("nonstationarity").size(); + const default_type empirical_skew = get_option_value("skew_nonstationarity", EMPIRICAL_SKEW_DEFAULT); // Load design matrix - const matrix_type design = File::Matrix::load_matrix (argument[2]); + const matrix_type design = File::Matrix::load_matrix(argument[2]); if (index_type(design.rows()) != importer.size()) - throw Exception ("number of subjects (" + str(importer.size()) + ") does not match number of rows in design matrix (" + str(design.rows()) + ")"); + throw Exception("number of subjects (" + str(importer.size()) + + ") does not match number of rows in design matrix (" + str(design.rows()) + ")"); // Before validating the contrast matrix, we first need to see if there are any // additional design matrix columns coming from edge-wise subject data vector extra_columns; bool nans_in_columns = false; - auto opt = get_options ("column"); + auto opt = get_options("column"); for (size_t i = 0; i != opt.size(); ++i) { - extra_columns.push_back (CohortDataImport()); - extra_columns[i].initialise (opt[i][0]); + extra_columns.push_back(CohortDataImport()); + extra_columns[i].initialise(opt[i][0]); if (!extra_columns[i].allFinite()) nans_in_columns = true; } const index_type num_factors = design.cols() + extra_columns.size(); - CONSOLE ("Number of factors: " + str(num_factors)); + CONSOLE("Number of factors: " + str(num_factors)); if (extra_columns.size()) { - CONSOLE ("Number of element-wise design matrix columns: " + str(extra_columns.size())); + CONSOLE("Number of element-wise design matrix columns: " + str(extra_columns.size())); if (nans_in_columns) - CONSOLE ("Non-finite values detected in element-wise design matrix columns; individual rows will be removed from edge-wise design matrices accordingly"); + CONSOLE("Non-finite values detected in element-wise design matrix columns; individual rows will be removed from " + "edge-wise design matrices accordingly"); } - check_design (design, extra_columns.size()); + check_design(design, extra_columns.size()); // Load variance groups - auto variance_groups = GLM::load_variance_groups (design.rows()); - const index_type num_vgs = variance_groups.size() ? variance_groups.maxCoeff()+1 : 1; + auto variance_groups = GLM::load_variance_groups(design.rows()); + const index_type num_vgs = variance_groups.size() ? variance_groups.maxCoeff() + 1 : 1; if (num_vgs > 1) - CONSOLE ("Number of variance groups: " + str(num_vgs)); + CONSOLE("Number of variance groups: " + str(num_vgs)); // Load hypotheses - const vector hypotheses = Math::Stats::GLM::load_hypotheses (argument[3]); + const vector hypotheses = Math::Stats::GLM::load_hypotheses(argument[3]); const index_type num_hypotheses = hypotheses.size(); if (hypotheses[0].cols() != num_factors) - throw Exception ("the number of columns in the contrast matrix (" + str(hypotheses[0].cols()) + ")" - + " does not equal the number of columns in the design matrix (" + str(design.cols()) + ")" - + (extra_columns.size() ? " (taking into account the " + str(extra_columns.size()) + " uses of -column)" : "")); - CONSOLE ("Number of hypotheses: " + str(num_hypotheses)); + throw Exception( + "the number of columns in the contrast matrix (" + str(hypotheses[0].cols()) + ")" + + " does not equal the number of columns in the design matrix (" + str(design.cols()) + ")" + + (extra_columns.size() ? " (taking into account the " + str(extra_columns.size()) + " uses of -column)" : "")); + CONSOLE("Number of hypotheses: " + str(num_hypotheses)); const std::string output_prefix = argument[4]; @@ -265,54 +251,57 @@ void run() // For compatibility with existing statistics code, symmetric matrix data is adjusted // into vector form - one row per edge in the symmetric connectome. This has already // been performed when the CohortDataImport class is initialised. - matrix_type data (importer.size(), num_edges); + matrix_type data(importer.size(), num_edges); { - ProgressBar progress ("Agglomerating input connectome data", importer.size()); + ProgressBar progress("Agglomerating input connectome data", importer.size()); for (index_type subject = 0; subject < importer.size(); subject++) { - (*importer[subject]) (data.row (subject)); + (*importer[subject])(data.row(subject)); ++progress; } } const bool nans_in_data = !data.allFinite(); // Only add contrast matrix row number to image outputs if there's more than one hypothesis - auto postfix = [&] (const index_type i) { return (num_hypotheses > 1) ? ("_" + hypotheses[i].name()) : ""; }; + auto postfix = [&](const index_type i) { return (num_hypotheses > 1) ? ("_" + hypotheses[i].name()) : ""; }; { - matrix_type betas (num_factors, num_edges); - matrix_type abs_effect_size (num_edges, num_hypotheses); - matrix_type std_effect_size (num_edges, num_hypotheses); - matrix_type stdev (num_vgs, num_edges); - vector_type cond (num_edges); + matrix_type betas(num_factors, num_edges); + matrix_type abs_effect_size(num_edges, num_hypotheses); + matrix_type std_effect_size(num_edges, num_hypotheses); + matrix_type stdev(num_vgs, num_edges); + vector_type cond(num_edges); - Math::Stats::GLM::all_stats (data, design, extra_columns, hypotheses, variance_groups, - cond, betas, abs_effect_size, std_effect_size, stdev); + Math::Stats::GLM::all_stats( + data, design, extra_columns, hypotheses, variance_groups, cond, betas, abs_effect_size, std_effect_size, stdev); - ProgressBar progress ("outputting beta coefficients, effect size and standard deviation", num_factors + (2 * num_hypotheses) + num_vgs + (nans_in_data || extra_columns.size() ? 1 : 0)); + ProgressBar progress("outputting beta coefficients, effect size and standard deviation", + num_factors + (2 * num_hypotheses) + num_vgs + (nans_in_data || extra_columns.size() ? 1 : 0)); for (index_type i = 0; i != num_factors; ++i) { - File::Matrix::save_matrix (mat2vec.V2M (betas.row(i)), output_prefix + "beta_" + str(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(betas.row(i)), output_prefix + "beta_" + str(i) + ".csv"); ++progress; } for (index_type i = 0; i != num_hypotheses; ++i) { if (!hypotheses[i].is_F()) { - File::Matrix::save_matrix (mat2vec.V2M (abs_effect_size.col(i)), output_prefix + "abs_effect" + postfix(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(abs_effect_size.col(i)), + output_prefix + "abs_effect" + postfix(i) + ".csv"); ++progress; if (num_vgs == 1) - File::Matrix::save_matrix (mat2vec.V2M (std_effect_size.col(i)), output_prefix + "std_effect" + postfix(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(std_effect_size.col(i)), + output_prefix + "std_effect" + postfix(i) + ".csv"); } else { ++progress; } ++progress; } if (nans_in_data || extra_columns.size()) { - File::Matrix::save_matrix (mat2vec.V2M (cond), output_prefix + "cond.csv"); + File::Matrix::save_matrix(mat2vec.V2M(cond), output_prefix + "cond.csv"); ++progress; } if (num_vgs == 1) { - File::Matrix::save_matrix (mat2vec.V2M (stdev.row(0)), output_prefix + "std_dev.csv"); + File::Matrix::save_matrix(mat2vec.V2M(stdev.row(0)), output_prefix + "std_dev.csv"); } else { for (index_type i = 0; i != num_vgs; ++i) { - File::Matrix::save_matrix (mat2vec.V2M (stdev.row(i)), output_prefix + "std_dev" + str(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(stdev.row(i)), output_prefix + "std_dev" + str(i) + ".csv"); ++progress; } } @@ -322,59 +311,71 @@ void run() std::shared_ptr glm_test; if (extra_columns.size() || nans_in_data) { if (variance_groups.size()) - glm_test.reset (new GLM::TestVariableHeteroscedastic (extra_columns, data, design, hypotheses, variance_groups, nans_in_data, nans_in_columns)); + glm_test.reset(new GLM::TestVariableHeteroscedastic( + extra_columns, data, design, hypotheses, variance_groups, nans_in_data, nans_in_columns)); else - glm_test.reset (new GLM::TestVariableHomoscedastic (extra_columns, data, design, hypotheses, nans_in_data, nans_in_columns)); + glm_test.reset( + new GLM::TestVariableHomoscedastic(extra_columns, data, design, hypotheses, nans_in_data, nans_in_columns)); } else { if (variance_groups.size()) - glm_test.reset (new GLM::TestFixedHeteroscedastic (data, design, hypotheses, variance_groups)); + glm_test.reset(new GLM::TestFixedHeteroscedastic(data, design, hypotheses, variance_groups)); else - glm_test.reset (new GLM::TestFixedHomoscedastic (data, design, hypotheses)); + glm_test.reset(new GLM::TestFixedHomoscedastic(data, design, hypotheses)); } // If performing non-stationarity adjustment we need to pre-compute the empirical statistic matrix_type empirical_statistic; if (do_nonstationarity_adjustment) { - empirical_statistic = matrix_type::Zero (num_edges, num_hypotheses); - Stats::PermTest::precompute_empirical_stat (glm_test, enhancer, empirical_skew, empirical_statistic); + empirical_statistic = matrix_type::Zero(num_edges, num_hypotheses); + Stats::PermTest::precompute_empirical_stat(glm_test, enhancer, empirical_skew, empirical_statistic); for (index_type i = 0; i != num_hypotheses; ++i) - File::Matrix::save_matrix (mat2vec.V2M (empirical_statistic.col(i)), output_prefix + "empirical" + postfix(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(empirical_statistic.col(i)), + output_prefix + "empirical" + postfix(i) + ".csv"); } // Precompute default statistic, Z-transformation of such, and enhanced statistic matrix_type default_statistic, default_zstat, default_enhanced; - Stats::PermTest::precompute_default_permutation (glm_test, enhancer, empirical_statistic, default_statistic, default_zstat, default_enhanced); + Stats::PermTest::precompute_default_permutation( + glm_test, enhancer, empirical_statistic, default_statistic, default_zstat, default_enhanced); for (index_type i = 0; i != num_hypotheses; ++i) { - File::Matrix::save_matrix (mat2vec.V2M (default_statistic.col(i)), output_prefix + (hypotheses[i].is_F() ? "F" : "t") + "value" + postfix(i) + ".csv"); - File::Matrix::save_matrix (mat2vec.V2M (default_zstat .col(i)), output_prefix + "Zstat" + postfix(i) + ".csv"); - File::Matrix::save_matrix (mat2vec.V2M (default_enhanced .col(i)), output_prefix + "enhanced" + postfix(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(default_statistic.col(i)), + output_prefix + (hypotheses[i].is_F() ? "F" : "t") + "value" + postfix(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(default_zstat.col(i)), output_prefix + "Zstat" + postfix(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(default_enhanced.col(i)), output_prefix + "enhanced" + postfix(i) + ".csv"); } // Perform permutation testing - if (!get_options ("notest").size()) { + if (!get_options("notest").size()) { - const bool fwe_strong = get_options ("strong").size(); + const bool fwe_strong = get_options("strong").size(); if (fwe_strong && num_hypotheses == 1) { WARN("Option -strong has no effect when testing a single hypothesis only"); } matrix_type null_distribution, uncorrected_pvalues; count_matrix_type null_contributions; - Stats::PermTest::run_permutations (glm_test, enhancer, empirical_statistic, default_enhanced, fwe_strong, - null_distribution, null_contributions, uncorrected_pvalues); + Stats::PermTest::run_permutations(glm_test, + enhancer, + empirical_statistic, + default_enhanced, + fwe_strong, + null_distribution, + null_contributions, + uncorrected_pvalues); if (fwe_strong) { - File::Matrix::save_vector (null_distribution.col(0), output_prefix + "null_dist.txt"); + File::Matrix::save_vector(null_distribution.col(0), output_prefix + "null_dist.txt"); } else { for (index_type i = 0; i != num_hypotheses; ++i) - File::Matrix::save_vector (null_distribution.col(i), output_prefix + "null_dist" + postfix(i) + ".txt"); + File::Matrix::save_vector(null_distribution.col(i), output_prefix + "null_dist" + postfix(i) + ".txt"); } - const matrix_type pvalue_output = MR::Math::Stats::fwe_pvalue (null_distribution, default_enhanced); + const matrix_type pvalue_output = MR::Math::Stats::fwe_pvalue(null_distribution, default_enhanced); for (index_type i = 0; i != num_hypotheses; ++i) { - File::Matrix::save_matrix (mat2vec.V2M (pvalue_output.col(i)), output_prefix + "fwe_1mpvalue" + postfix(i) + ".csv"); - File::Matrix::save_matrix (mat2vec.V2M (uncorrected_pvalues.col(i)), output_prefix + "uncorrected_1mpvalue" + postfix(i) + ".csv"); - File::Matrix::save_matrix (mat2vec.V2M (null_contributions.col(i)), output_prefix + "null_contributions" + postfix(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(pvalue_output.col(i)), + output_prefix + "fwe_1mpvalue" + postfix(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(uncorrected_pvalues.col(i)), + output_prefix + "uncorrected_1mpvalue" + postfix(i) + ".csv"); + File::Matrix::save_matrix(mat2vec.V2M(null_contributions.col(i)), + output_prefix + "null_contributions" + postfix(i) + ".csv"); } - } - } diff --git a/cmd/dcmedit.cpp b/cmd/dcmedit.cpp index 1e3626d75e..2c6143bf79 100644 --- a/cmd/dcmedit.cpp +++ b/cmd/dcmedit.cpp @@ -16,129 +16,119 @@ #include "command.h" #include "debug.h" -#include "file/path.h" +#include "file/dicom/definitions.h" #include "file/dicom/element.h" #include "file/dicom/quick_scan.h" -#include "file/dicom/definitions.h" - +#include "file/path.h" using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Edit DICOM file in-place"; DESCRIPTION - + "Note that this command simply replaces the existing " - "values without modifying the DICOM structure in any way. Replacement text " - "will be truncated if it is too long to fit inside the existing tag." - - + "WARNING: this command will modify existing data! It is recommended to run " - "this command on a copy of the original data set to avoid loss of data."; + +"Note that this command simply replaces the existing " + "values without modifying the DICOM structure in any way. Replacement text " + "will be truncated if it is too long to fit inside the existing tag." + + "WARNING: this command will modify existing data! It is recommended to run " + "this command on a copy of the original data set to avoid loss of data."; ARGUMENTS - + Argument ("file", "the DICOM file to be edited.").type_file_in(); + +Argument("file", "the DICOM file to be edited.").type_file_in(); OPTIONS - + Option ("anonymise", "remove any identifiable information, by replacing the following tags:\n" - "- any tag with Value Representation PN will be replaced with 'anonymous'\n" - "- tag (0010,0030) PatientBirthDate will be replaced with an empty string\n" - "WARNING: there is no guarantee that this command will remove all identiable " - "information, since such information may be contained in any number " - "of private vendor-specific tags. You will need to double-check the " - "results independently if you " "need to ensure anonymity.") - - + Option ("id", "replace all ID tags with string supplied. This consists of tags " - "(0010, 0020) PatientID and (0010, 1000) OtherPatientIDs") - + Argument ("text").type_text() - - + Option ("tag", "replace specific tag.").allow_multiple() - + Argument ("group") - + Argument ("element") - + Argument ("newvalue"); + +Option("anonymise", + "remove any identifiable information, by replacing the following tags:\n" + "- any tag with Value Representation PN will be replaced with 'anonymous'\n" + "- tag (0010,0030) PatientBirthDate will be replaced with an empty string\n" + "WARNING: there is no guarantee that this command will remove all identiable " + "information, since such information may be contained in any number " + "of private vendor-specific tags. You will need to double-check the " + "results independently if you " + "need to ensure anonymity.") + + + Option("id", + "replace all ID tags with string supplied. This consists of tags " + "(0010, 0020) PatientID and (0010, 1000) OtherPatientIDs") + + Argument("text").type_text() + + + Option("tag", "replace specific tag.").allow_multiple() + Argument("group") + Argument("element") + + Argument("newvalue"); } - -class Tag { - public: - Tag (uint16_t group, uint16_t element, const std::string& newvalue) : - group (group), element (element), newvalue (newvalue) { } - uint16_t group, element; - std::string newvalue; +class Tag { +public: + Tag(uint16_t group, uint16_t element, const std::string &newvalue) + : group(group), element(element), newvalue(newvalue) {} + uint16_t group, element; + std::string newvalue; }; - - - -inline std::string hex (uint16_t value) -{ +inline std::string hex(uint16_t value) { std::ostringstream hex; hex << std::hex << value; return hex.str(); } -inline uint16_t read_hex (const std::string& m) -{ +inline uint16_t read_hex(const std::string &m) { uint16_t value; - std::istringstream hex (m); + std::istringstream hex(m); hex >> std::hex >> value; return value; } - - - -void run () -{ +void run() { vector tags; vector VRs; - auto opt = get_options ("anonymise"); + auto opt = get_options("anonymise"); if (opt.size()) { - tags.push_back (Tag (0x0010U, 0x0030U, "")); // PatientBirthDate - VRs.push_back (VR_PN); + tags.push_back(Tag(0x0010U, 0x0030U, "")); // PatientBirthDate + VRs.push_back(VR_PN); } - - opt = get_options ("tag"); + opt = get_options("tag"); if (opt.size()) for (size_t n = 0; n < opt.size(); ++n) - tags.push_back (Tag (read_hex (opt[n][0]), read_hex (opt[n][1]), opt[n][2])); + tags.push_back(Tag(read_hex(opt[n][0]), read_hex(opt[n][1]), opt[n][2])); - opt = get_options ("id"); + opt = get_options("id"); if (opt.size()) { std::string newid = opt[0][0]; - tags.push_back (Tag (0x0010U, 0x0020U, newid)); // PatientID - tags.push_back (Tag (0x0010U, 0x1000U, newid)); // OtherPatientIDs + tags.push_back(Tag(0x0010U, 0x0020U, newid)); // PatientID + tags.push_back(Tag(0x0010U, 0x1000U, newid)); // OtherPatientIDs } for (size_t n = 0; n < VRs.size(); ++n) { - union __VR { uint16_t i; char c[2]; } VR; + union __VR { + uint16_t i; + char c[2]; + } VR; VR.i = VRs[n]; - INFO (std::string ("clearing entries with VR \"") + VR.c[1] + VR.c[0] + "\""); + INFO(std::string("clearing entries with VR \"") + VR.c[1] + VR.c[0] + "\""); } for (size_t n = 0; n < tags.size(); ++n) - INFO ("replacing tag (" + hex(tags[n].group) + "," + hex(tags[n].element) + ") with value \"" + tags[n].newvalue + "\""); + INFO("replacing tag (" + hex(tags[n].group) + "," + hex(tags[n].element) + ") with value \"" + tags[n].newvalue + + "\""); File::Dicom::Element item; - item.set (argument[0], true, true); + item.set(argument[0], true, true); while (item.read()) { for (size_t n = 0; n < VRs.size(); ++n) { if (item.VR == VRs[n]) { - memset (item.data, 32, item.size); - memcpy (item.data, "anonymous", std::min (item.size, 9)); + memset(item.data, 32, item.size); + memcpy(item.data, "anonymous", std::min(item.size, 9)); } } for (size_t n = 0; n < tags.size(); ++n) { - if (item.is (tags[n].group, tags[n].element)) { - memset (item.data, 32, item.size); - memcpy (item.data, tags[n].newvalue.c_str(), std::min (item.size, tags[n].newvalue.size())); + if (item.is(tags[n].group, tags[n].element)) { + memset(item.data, 32, item.size); + memcpy(item.data, tags[n].newvalue.c_str(), std::min(item.size, tags[n].newvalue.size())); } } } } - diff --git a/cmd/dcminfo.cpp b/cmd/dcminfo.cpp index d318cf826c..9f04617530 100644 --- a/cmd/dcminfo.cpp +++ b/cmd/dcminfo.cpp @@ -16,75 +16,69 @@ #include "command.h" #include "debug.h" -#include "file/path.h" #include "file/dicom/element.h" #include "file/dicom/quick_scan.h" - +#include "file/path.h" using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Output DICOM fields in human-readable format"; ARGUMENTS - + Argument ("file", "the DICOM file to be scanned.").type_file_in(); + +Argument("file", "the DICOM file to be scanned.").type_file_in(); OPTIONS - + Option ("all", "print all DICOM fields.") + +Option("all", "print all DICOM fields.") - + Option ("csa", "print all Siemens CSA fields (excluding Phoenix unless requested)") - + Option ("phoenix", "print Siemens Phoenix protocol information") + + Option("csa", "print all Siemens CSA fields (excluding Phoenix unless requested)") + + Option("phoenix", "print Siemens Phoenix protocol information") - + Option ("tag", "print field specified by the group & element tags supplied. " - "Tags should be supplied as Hexadecimal (i.e. as they appear in the -all listing).") - .allow_multiple() - + Argument ("group").type_text() - + Argument ("element").type_text(); + + Option("tag", + "print field specified by the group & element tags supplied. " + "Tags should be supplied as Hexadecimal (i.e. as they appear in the -all listing).") + .allow_multiple() + + Argument("group").type_text() + Argument("element").type_text(); } - -class Tag { - public: - uint16_t group, element; - std::string value; +class Tag { +public: + uint16_t group, element; + std::string value; }; -inline uint16_t read_hex (const std::string& m) -{ +inline uint16_t read_hex(const std::string &m) { uint16_t value; - std::istringstream hex (m); + std::istringstream hex(m); hex >> std::hex >> value; return value; } -void run () -{ +void run() { auto opt = get_options("tag"); if (opt.size()) { std::istringstream hex; - vector tags (opt.size()); + vector tags(opt.size()); for (size_t n = 0; n < opt.size(); ++n) { - tags[n].group = read_hex (opt[n][0]); - tags[n].element = read_hex (opt[n][1]); + tags[n].group = read_hex(opt[n][0]); + tags[n].element = read_hex(opt[n][1]); } File::Dicom::Element item; - item.set (argument[0], true); + item.set(argument[0], true); while (item.read()) { for (size_t n = 0; n < opt.size(); ++n) - if (item.is (tags[n].group, tags[n].element)) - std::cout << MR::printf ("[%04X,%04X] ", tags[n].group, tags[n].element) << item.as_string() << "\n"; + if (item.is(tags[n].group, tags[n].element)) + std::cout << MR::printf("[%04X,%04X] ", tags[n].group, tags[n].element) << item.as_string() << "\n"; } return; } - File::Dicom::QuickScan reader; const bool all = get_options("all").size(); @@ -92,12 +86,11 @@ void run () const bool phoenix = get_options("phoenix").size(); if (all) - print (File::Dicom::Element::print_header()); + print(File::Dicom::Element::print_header()); - if (reader.read (argument[0], all, csa, phoenix, true)) - throw Exception ("error reading file \"" + reader.filename + "\""); + if (reader.read(argument[0], all, csa, phoenix, true)) + throw Exception("error reading file \"" + reader.filename + "\""); if (!all && !csa && !phoenix) std::cout << reader; } - diff --git a/cmd/dirflip.cpp b/cmd/dirflip.cpp index e739e6d556..76177c6c3e 100644 --- a/cmd/dirflip.cpp +++ b/cmd/dirflip.cpp @@ -15,151 +15,126 @@ */ #include "command.h" -#include "progressbar.h" -#include "math/rng.h" -#include "math/SH.h" +#include "dwi/directions/file.h" #include "file/utils.h" +#include "math/SH.h" +#include "math/rng.h" +#include "progressbar.h" #include "thread.h" -#include "dwi/directions/file.h" constexpr size_t default_permutations = 1e8; - - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Invert the polarity of individual directions so as to optimise a unipolar electrostatic repulsion model"; DESCRIPTION - + "The orientations themselves are not affected, only their " - "polarity; this is necessary to ensure near-optimal distribution of DW " - "directions for eddy-current correction."; + +"The orientations themselves are not affected, only their " + "polarity; this is necessary to ensure near-optimal distribution of DW " + "directions for eddy-current correction."; ARGUMENTS - + Argument ("in", "the input files for the directions.").type_file_in() - + Argument ("out", "the output files for the directions.").type_file_out(); - + +Argument("in", "the input files for the directions.").type_file_in() + + Argument("out", "the output files for the directions.").type_file_out(); OPTIONS - + Option ("permutations", "number of permutations to try (default: " + str(default_permutations) + ")") - + Argument ("num").type_integer (1) + +Option("permutations", "number of permutations to try (default: " + str(default_permutations) + ")") + + Argument("num").type_integer(1) - + Option ("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); + + Option("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); } - using value_type = double; using vector3_type = Eigen::Vector3d; - -class Shared { - public: - Shared (const Eigen::MatrixXd& directions, size_t target_num_permutations) : - directions (directions), target_num_permutations (target_num_permutations), num_permutations(0), - progress ("optimising directions for eddy-currents", target_num_permutations), - best_signs (directions.rows(), 1), best_eddy (std::numeric_limits::max()) { } - - bool update (value_type eddy, const vector& signs) - { - std::lock_guard lock (mutex); - if (eddy < best_eddy) { - best_eddy = eddy; - best_signs = signs; - progress.set_text ("optimising directions for eddy-currents (current best configuration: energy = " + str(best_eddy) + ")"); - } - ++num_permutations; - ++progress; - return num_permutations < target_num_permutations; - } - - - - value_type eddy (size_t i, size_t j, const vector& signs) const { - vector3_type a = { directions(i,0), directions(i,1), directions(i,2) }; - vector3_type b = { directions(j,0), directions(j,1), directions(j,2) }; - if (signs[i] < 0) a = -a; - if (signs[j] < 0) b = -b; - return 1.0 / (a-b).norm(); +class Shared { +public: + Shared(const Eigen::MatrixXd &directions, size_t target_num_permutations) + : directions(directions), + target_num_permutations(target_num_permutations), + num_permutations(0), + progress("optimising directions for eddy-currents", target_num_permutations), + best_signs(directions.rows(), 1), + best_eddy(std::numeric_limits::max()) {} + + bool update(value_type eddy, const vector &signs) { + std::lock_guard lock(mutex); + if (eddy < best_eddy) { + best_eddy = eddy; + best_signs = signs; + progress.set_text( + "optimising directions for eddy-currents (current best configuration: energy = " + str(best_eddy) + ")"); } + ++num_permutations; + ++progress; + return num_permutations < target_num_permutations; + } + value_type eddy(size_t i, size_t j, const vector &signs) const { + vector3_type a = {directions(i, 0), directions(i, 1), directions(i, 2)}; + vector3_type b = {directions(j, 0), directions(j, 1), directions(j, 2)}; + if (signs[i] < 0) + a = -a; + if (signs[j] < 0) + b = -b; + return 1.0 / (a - b).norm(); + } - vector get_init_signs () const { return vector (directions.rows(), 1); } - const vector& get_best_signs () const { return best_signs; } - - - protected: - const Eigen::MatrixXd& directions; - const size_t target_num_permutations; - size_t num_permutations; - ProgressBar progress; - vector best_signs; - value_type best_eddy; - std::mutex mutex; - + vector get_init_signs() const { return vector(directions.rows(), 1); } + const vector &get_best_signs() const { return best_signs; } + +protected: + const Eigen::MatrixXd &directions; + const size_t target_num_permutations; + size_t num_permutations; + ProgressBar progress; + vector best_signs; + value_type best_eddy; + std::mutex mutex; }; +class Processor { +public: + Processor(Shared &shared) : shared(shared), signs(shared.get_init_signs()), uniform(0, signs.size() - 1) {} + void execute() { + while (eval()) + ; + } + void next_permutation() { signs[uniform(rng)] *= -1; } + bool eval() { + next_permutation(); -class Processor { - public: - Processor (Shared& shared) : - shared (shared), - signs (shared.get_init_signs()), - uniform (0, signs.size()-1) { } - - void execute () { - while (eval()); - } - - - void next_permutation () - { - signs[uniform(rng)] *= -1; - } - - bool eval () - { - next_permutation(); - - value_type eddy = 0.0; - for (size_t i = 0; i < signs.size(); ++i) - for (size_t j = i+1; j < signs.size(); ++j) - eddy += shared.eddy (i, j, signs); + value_type eddy = 0.0; + for (size_t i = 0; i < signs.size(); ++i) + for (size_t j = i + 1; j < signs.size(); ++j) + eddy += shared.eddy(i, j, signs); - return shared.update (eddy, signs); - } + return shared.update(eddy, signs); + } - protected: - Shared& shared; - vector signs; - Math::RNG rng; - std::uniform_int_distribution uniform; +protected: + Shared &shared; + vector signs; + Math::RNG rng; + std::uniform_int_distribution uniform; }; +void run() { + auto directions = DWI::Directions::load_cartesian(argument[0]); - - - - - - -void run () -{ - auto directions = DWI::Directions::load_cartesian (argument[0]); - - size_t num_permutations = get_option_value ("permutations", default_permutations); + size_t num_permutations = get_option_value("permutations", default_permutations); vector signs; { - Shared eddy_shared (directions, num_permutations); - Thread::run (Thread::multi (Processor (eddy_shared)), "eval thread"); + Shared eddy_shared(directions, num_permutations); + Thread::run(Thread::multi(Processor(eddy_shared)), "eval thread"); signs = eddy_shared.get_best_signs(); } @@ -168,8 +143,5 @@ void run () directions.row(n) *= -1.0; bool cartesian = get_options("cartesian").size(); - DWI::Directions::save (directions, argument[1], cartesian); + DWI::Directions::save(directions, argument[1], cartesian); } - - - diff --git a/cmd/dirgen.cpp b/cmd/dirgen.cpp index d2d3f3306d..445582b7a5 100644 --- a/cmd/dirgen.cpp +++ b/cmd/dirgen.cpp @@ -15,262 +15,229 @@ */ #include "command.h" -#include "progressbar.h" +#include "dwi/directions/file.h" +#include "math/check_gradient.h" +#include "math/gradient_descent.h" #include "math/rng.h" +#include "progressbar.h" #include "thread.h" -#include "math/gradient_descent.h" -#include "math/check_gradient.h" -#include "dwi/directions/file.h" #define DEFAULT_POWER 1 #define DEFAULT_NITER 10000 #define DEFAULT_RESTARTS 10 - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Generate a set of uniformly distributed directions using a bipolar electrostatic repulsion model"; DESCRIPTION - + "Directions are distributed by analogy to an electrostatic repulsion system, with each direction " - "corresponding to a single electrostatic charge (for -unipolar), or a pair of diametrically opposed charges " - "(for the default bipolar case). The energy of the system is determined based on the Coulomb repulsion, " - "which assumes the form 1/r^power, where r is the distance between any pair of charges, and p is the power " - "assumed for the repulsion law (default: 1). The minimum energy state is obtained by gradient descent."; - + +"Directions are distributed by analogy to an electrostatic repulsion system, with each direction " + "corresponding to a single electrostatic charge (for -unipolar), or a pair of diametrically opposed charges " + "(for the default bipolar case). The energy of the system is determined based on the Coulomb repulsion, " + "which assumes the form 1/r^power, where r is the distance between any pair of charges, and p is the power " + "assumed for the repulsion law (default: 1). The minimum energy state is obtained by gradient descent."; REFERENCES - + "Jones, D.; Horsfield, M. & Simmons, A. " - "Optimal strategies for measuring diffusion in anisotropic systems by magnetic resonance imaging. " - "Magnetic Resonance in Medicine, 1999, 42: 515-525" + +"Jones, D.; Horsfield, M. & Simmons, A. " + "Optimal strategies for measuring diffusion in anisotropic systems by magnetic resonance imaging. " + "Magnetic Resonance in Medicine, 1999, 42: 515-525" - + "Papadakis, N. G.; Murrills, C. D.; Hall, L. D.; Huang, C. L.-H. & Adrian Carpenter, T. " - "Minimal gradient encoding for robust estimation of diffusion anisotropy. " - "Magnetic Resonance Imaging, 2000, 18: 671-679"; + + "Papadakis, N. G.; Murrills, C. D.; Hall, L. D.; Huang, C. L.-H. & Adrian Carpenter, T. " + "Minimal gradient encoding for robust estimation of diffusion anisotropy. " + "Magnetic Resonance Imaging, 2000, 18: 671-679"; ARGUMENTS - + Argument ("ndir", "the number of directions to generate.").type_integer (6, std::numeric_limits::max()) - + Argument ("dirs", "the text file to write the directions to, as [ az el ] pairs.").type_file_out(); + +Argument("ndir", "the number of directions to generate.").type_integer(6, std::numeric_limits::max()) + + Argument("dirs", "the text file to write the directions to, as [ az el ] pairs.").type_file_out(); OPTIONS - + Option ("power", "specify exponent to use for repulsion power law (default: " + str(DEFAULT_POWER) + "). This must be a power of 2 (i.e. 1, 2, 4, 8, 16, ...).") - + Argument ("exp").type_integer (1, std::numeric_limits::max()) + +Option("power", + "specify exponent to use for repulsion power law (default: " + str(DEFAULT_POWER) + + "). This must be a power of 2 (i.e. 1, 2, 4, 8, 16, ...).") + + Argument("exp").type_integer(1, std::numeric_limits::max()) - + Option ("niter", "specify the maximum number of iterations to perform (default: " + str(DEFAULT_NITER) + ").") - + Argument ("num").type_integer (1, std::numeric_limits::max()) + + Option("niter", "specify the maximum number of iterations to perform (default: " + str(DEFAULT_NITER) + ").") + + Argument("num").type_integer(1, std::numeric_limits::max()) - + Option ("restarts", "specify the number of restarts to perform (default: " + str(DEFAULT_RESTARTS) + ").") - + Argument ("num").type_integer (1, std::numeric_limits::max()) + + Option("restarts", "specify the number of restarts to perform (default: " + str(DEFAULT_RESTARTS) + ").") + + Argument("num").type_integer(1, std::numeric_limits::max()) - + Option ("unipolar", "optimise assuming a unipolar electrostatic repulsion model rather than the bipolar model normally assumed in DWI") - - + Option ("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); + + Option("unipolar", + "optimise assuming a unipolar electrostatic repulsion model rather than the bipolar model normally " + "assumed in DWI") + + Option("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); } - - - // constrain directions to remain unit length: -class ProjectedUpdate { - public: - bool operator() ( - Eigen::VectorXd& newx, - const Eigen::VectorXd& x, - const Eigen::VectorXd& g, - double step_size) { - newx.noalias() = x - step_size * g; - for (ssize_t n = 0; n < newx.size(); n += 3) - newx.segment (n,3).normalize(); - return newx != x; - } +class ProjectedUpdate { +public: + bool operator()(Eigen::VectorXd &newx, const Eigen::VectorXd &x, const Eigen::VectorXd &g, double step_size) { + newx.noalias() = x - step_size * g; + for (ssize_t n = 0; n < newx.size(); n += 3) + newx.segment(n, 3).normalize(); + return newx != x; + } }; - - - - - - -class Energy { - public: - Energy (ProgressBar& progress) : - progress (progress), - ndirs (to (argument[0])), - bipolar (!(get_options ("unipolar").size())), - power (0), - directions (3 * ndirs) { } +class Energy { +public: + Energy(ProgressBar &progress) + : progress(progress), + ndirs(to(argument[0])), + bipolar(!(get_options("unipolar").size())), + power(0), + directions(3 * ndirs) {} // Non-optimised compilation can't handle recursive inline functions #ifdef __OPTIMIZE__ -FORCE_INLINE + FORCE_INLINE #endif - double fast_pow (double x, int p) { - return p == 1 ? x : fast_pow (x*x, p/2); - } - - using value_type = double; - - size_t size () const { return 3 * ndirs; } - - // set x to original directions provided in constructor. - // The idea is to save the directions from one run to initialise next run - // at higher power. - double init (Eigen::VectorXd& x) - { - Math::RNG::Normal rng; - for (size_t n = 0; n < ndirs; ++n) { - auto d = x.segment (3*n,3); - d[0] = rng(); - d[1] = rng(); - d[2] = rng(); - d.normalize(); - } - return 0.01; + double fast_pow(double x, int p) { return p == 1 ? x : fast_pow(x * x, p / 2); } + + using value_type = double; + + size_t size() const { return 3 * ndirs; } + + // set x to original directions provided in constructor. + // The idea is to save the directions from one run to initialise next run + // at higher power. + double init(Eigen::VectorXd &x) { + Math::RNG::Normal rng; + for (size_t n = 0; n < ndirs; ++n) { + auto d = x.segment(3 * n, 3); + d[0] = rng(); + d[1] = rng(); + d[2] = rng(); + d.normalize(); } + return 0.01; + } - - - // function executed by optimiser at each iteration: - double operator() (const Eigen::VectorXd& x, Eigen::VectorXd& g) { - double E = 0.0; - g.setZero(); - - for (size_t i = 0; i < ndirs-1; ++i) { - auto d1 = x.segment (3*i, 3); - auto g1 = g.segment (3*i, 3); - for (size_t j = i+1; j < ndirs; ++j) { - auto d2 = x.segment (3*j, 3); - auto g2 = g.segment (3*j, 3); - - Eigen::Vector3d r = d1-d2; - double _1_r2 = 1.0 / r.squaredNorm(); - double _1_r = std::sqrt (_1_r2); - double e = fast_pow (_1_r, power); + // function executed by optimiser at each iteration: + double operator()(const Eigen::VectorXd &x, Eigen::VectorXd &g) { + double E = 0.0; + g.setZero(); + + for (size_t i = 0; i < ndirs - 1; ++i) { + auto d1 = x.segment(3 * i, 3); + auto g1 = g.segment(3 * i, 3); + for (size_t j = i + 1; j < ndirs; ++j) { + auto d2 = x.segment(3 * j, 3); + auto g2 = g.segment(3 * j, 3); + + Eigen::Vector3d r = d1 - d2; + double _1_r2 = 1.0 / r.squaredNorm(); + double _1_r = std::sqrt(_1_r2); + double e = fast_pow(_1_r, power); + E += e; + g1 -= (power * e * _1_r2) * r; + g2 += (power * e * _1_r2) * r; + + if (bipolar) { + r = d1 + d2; + _1_r2 = 1.0 / r.squaredNorm(); + _1_r = std::sqrt(_1_r2); + e = fast_pow(_1_r, power); E += e; g1 -= (power * e * _1_r2) * r; - g2 += (power * e * _1_r2) * r; - - if (bipolar) { - r = d1+d2; - _1_r2 = 1.0 / r.squaredNorm(); - _1_r = std::sqrt (_1_r2); - e = fast_pow (_1_r, power); - E += e; - g1 -= (power * e * _1_r2) * r; - g2 -= (power * e * _1_r2) * r; - } - + g2 -= (power * e * _1_r2) * r; } } - - // constrain gradients to lie tangent to unit sphere: - for (size_t n = 0; n < ndirs; ++n) - g.segment(3*n,3) -= x.segment(3*n,3).dot (g.segment(3*n,3)) * x.segment(3*n,3); - - return E; } + // constrain gradients to lie tangent to unit sphere: + for (size_t n = 0; n < ndirs; ++n) + g.segment(3 * n, 3) -= x.segment(3 * n, 3).dot(g.segment(3 * n, 3)) * x.segment(3 * n, 3); + return E; + } - // function executed per thread: - void execute () - { - size_t this_start = 0; - while ((this_start = current_start++) < restarts) { - INFO ("launching start " + str (this_start)); - double E = 0.0; - - for (power = 1; power <= target_power; power *= 2) { - Math::GradientDescent optim (*this, ProjectedUpdate()); + // function executed per thread: + void execute() { + size_t this_start = 0; + while ((this_start = current_start++) < restarts) { + INFO("launching start " + str(this_start)); + double E = 0.0; - INFO ("start " + str(this_start) + ": setting power = " + str (power)); - optim.init(); + for (power = 1; power <= target_power; power *= 2) { + Math::GradientDescent optim(*this, ProjectedUpdate()); - size_t iter = 0; - for (; iter < niter; iter++) { - if (!optim.iterate()) - break; + INFO("start " + str(this_start) + ": setting power = " + str(power)); + optim.init(); - DEBUG ("start " + str(this_start) + ": [ " + str (iter) + " ] (pow = " + str (power) + ") E = " + str (optim.value(), 8) - + ", grad = " + str (optim.gradient_norm(), 8)); + size_t iter = 0; + for (; iter < niter; iter++) { + if (!optim.iterate()) + break; - std::lock_guard lock (mutex); - ++progress; - } + DEBUG("start " + str(this_start) + ": [ " + str(iter) + " ] (pow = " + str(power) + + ") E = " + str(optim.value(), 8) + ", grad = " + str(optim.gradient_norm(), 8)); - directions = optim.state(); - E = optim.value(); + std::lock_guard lock(mutex); + ++progress; } + directions = optim.state(); + E = optim.value(); + } - - std::lock_guard lock (mutex); - if (E < best_E) { - best_E = E; - best_directions = directions; - } + std::lock_guard lock(mutex); + if (E < best_E) { + best_E = E; + best_directions = directions; } } + } - - static size_t restarts; - static int target_power; - static size_t niter; - static double best_E; - static Eigen::VectorXd best_directions; - - protected: - ProgressBar& progress; - size_t ndirs; - bool bipolar; - int power; - Eigen::VectorXd directions; - double E; - - static std::mutex mutex; - static std::atomic current_start; + static size_t restarts; + static int target_power; + static size_t niter; + static double best_E; + static Eigen::VectorXd best_directions; + +protected: + ProgressBar &progress; + size_t ndirs; + bool bipolar; + int power; + Eigen::VectorXd directions; + double E; + + static std::mutex mutex; + static std::atomic current_start; }; - -size_t Energy::restarts (DEFAULT_RESTARTS); -int Energy::target_power (DEFAULT_POWER); -size_t Energy::niter (DEFAULT_NITER); +size_t Energy::restarts(DEFAULT_RESTARTS); +int Energy::target_power(DEFAULT_POWER); +size_t Energy::niter(DEFAULT_NITER); std::mutex Energy::mutex; -std::atomic Energy::current_start (0); +std::atomic Energy::current_start(0); double Energy::best_E = std::numeric_limits::infinity(); Eigen::VectorXd Energy::best_directions; - - - -void run () -{ - Energy::restarts = get_option_value ("restarts", DEFAULT_RESTARTS); - Energy::target_power = get_option_value ("power", DEFAULT_POWER); - Energy::niter = get_option_value ("niter", DEFAULT_NITER); +void run() { + Energy::restarts = get_option_value("restarts", DEFAULT_RESTARTS); + Energy::target_power = get_option_value("power", DEFAULT_POWER); + Energy::niter = get_option_value("niter", DEFAULT_NITER); { - ProgressBar progress ("Optimising directions up to power " + str(Energy::target_power) + " (" + str(Energy::restarts) + " restarts)"); - Energy energy_functor (progress); - auto threads = Thread::run (Thread::multi (energy_functor), "energy function"); + ProgressBar progress("Optimising directions up to power " + str(Energy::target_power) + " (" + + str(Energy::restarts) + " restarts)"); + Energy energy_functor(progress); + auto threads = Thread::run(Thread::multi(energy_functor), "energy function"); } - CONSOLE ("final energy = " + str(Energy::best_E)); - size_t ndirs = Energy::best_directions.size()/3; - Eigen::MatrixXd directions_matrix (ndirs, 3); + CONSOLE("final energy = " + str(Energy::best_E)); + size_t ndirs = Energy::best_directions.size() / 3; + Eigen::MatrixXd directions_matrix(ndirs, 3); for (size_t n = 0; n < ndirs; ++n) - directions_matrix.row (n) = Energy::best_directions.segment (3*n, 3); + directions_matrix.row(n) = Energy::best_directions.segment(3 * n, 3); - DWI::Directions::save (directions_matrix, argument[1], get_options ("cartesian").size()); + DWI::Directions::save(directions_matrix, argument[1], get_options("cartesian").size()); } - - - - diff --git a/cmd/dirmerge.cpp b/cmd/dirmerge.cpp index e1e9684a2a..67b723b512 100644 --- a/cmd/dirmerge.cpp +++ b/cmd/dirmerge.cpp @@ -15,134 +15,132 @@ */ #include "command.h" -#include "progressbar.h" -#include "math/rng.h" #include "dwi/directions/file.h" #include "file/ofstream.h" +#include "math/rng.h" +#include "progressbar.h" +#include #include #include -#include using namespace MR; using namespace App; -void usage () -{ -AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; - -SYNOPSIS = "Splice / merge multiple sets of directions in such a way as to maintain near-optimality upon truncation"; - -ARGUMENTS - + Argument ("subsets", "the number of subsets (eg. phase encoding directions) per b-value").type_integer(1,10000) - + Argument ("bvalue files", "the b-value and sets of corresponding files, in order").type_text().allow_multiple() - + Argument ("out", "the output directions file, with each row listing " - "the X Y Z gradient directions, the b-value, and an index representing " - "the phase encode direction").type_file_out(); - -OPTIONS - + Option ("unipolar_weight", - "set the weight given to the unipolar electrostatic repulsion model compared to the " - "bipolar model (default: 0.2).") - + Argument ("value").type_float(0.0, 1.0); +void usage() { + AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; + + SYNOPSIS = "Splice / merge multiple sets of directions in such a way as to maintain near-optimality upon truncation"; + + ARGUMENTS + +Argument("subsets", "the number of subsets (eg. phase encoding directions) per b-value").type_integer(1, 10000) + + Argument("bvalue files", "the b-value and sets of corresponding files, in order").type_text().allow_multiple() + + Argument("out", + "the output directions file, with each row listing " + "the X Y Z gradient directions, the b-value, and an index representing " + "the phase encode direction") + .type_file_out(); + + OPTIONS + +Option("unipolar_weight", + "set the weight given to the unipolar electrostatic repulsion model compared to the " + "bipolar model (default: 0.2).") + + Argument("value").type_float(0.0, 1.0); } - - using value_type = double; -using Direction = Eigen::Matrix; +using Direction = Eigen::Matrix; using DirectionSet = vector; - -struct OutDir { +struct OutDir { Direction d; size_t b; size_t pe; }; -inline std::ostream& operator<< (std::ostream& stream, const OutDir& d) { +inline std::ostream &operator<<(std::ostream &stream, const OutDir &d) { stream << "[ " << d.d << "], " << d.b << ", " << d.pe << " ]"; return stream; } - -void run () -{ +void run() { size_t num_subsets = argument[0]; - value_type unipolar_weight = App::get_option_value ("unipolar_weight", 0.2); + value_type unipolar_weight = App::get_option_value("unipolar_weight", 0.2); value_type bipolar_weight = 1.0 - unipolar_weight; - vector> dirs; - vector bvalue ((argument.size() - 2) / (1+num_subsets)); - INFO ("expecting " + str(bvalue.size()) + " b-values"); - if (bvalue.size()*(1+num_subsets) + 2 != argument.size()) - throw Exception ("inconsistent number of arguments"); - + vector bvalue((argument.size() - 2) / (1 + num_subsets)); + INFO("expecting " + str(bvalue.size()) + " b-values"); + if (bvalue.size() * (1 + num_subsets) + 2 != argument.size()) + throw Exception("inconsistent number of arguments"); // read them in: size_t current = 1, nb = 0; - while (current < argument.size()-1) { - bvalue[nb] = to (argument[current++]); + while (current < argument.size() - 1) { + bvalue[nb] = to(argument[current++]); vector d; for (size_t i = 0; i < num_subsets; ++i) { - auto m = DWI::Directions::load_cartesian (argument[current++]); + auto m = DWI::Directions::load_cartesian(argument[current++]); DirectionSet set; for (ssize_t r = 0; r < m.rows(); ++r) - set.push_back (Direction (m(r,0), m(r,1), m(r,2))); - d.push_back (set); + set.push_back(Direction(m(r, 0), m(r, 1), m(r, 2))); + d.push_back(set); } - INFO ("found b = " + str(bvalue[nb]) + ", " + - str ([&]{ vector s; for (auto& n : d) s.push_back (n.size()); return s; }()) + " volumes"); - - dirs.push_back (d); + INFO("found b = " + str(bvalue[nb]) + ", " + str([&] { + vector s; + for (auto &n : d) + s.push_back(n.size()); + return s; + }()) + + " volumes"); + + dirs.push_back(d); ++nb; } - size_t total = [&]{ size_t n = 0; for (auto& d : dirs) for (auto& m : d) n += m.size(); return n; }(); - INFO ("found total of " + str(total) + " volumes") ; - + size_t total = [&] { + size_t n = 0; + for (auto &d : dirs) + for (auto &m : d) + n += m.size(); + return n; + }(); + INFO("found total of " + str(total) + " volumes"); // pick random direction from first direction set: std::random_device rd; - std::mt19937 rng (rd()); - size_t first = std::uniform_int_distribution (0, dirs[0][0].size()-1)(rng); - + std::mt19937 rng(rd()); + size_t first = std::uniform_int_distribution(0, dirs[0][0].size() - 1)(rng); vector merged; - auto push = [&](size_t b, size_t p, size_t n) - { - merged.push_back ({ Direction (dirs[b][p][n][0], dirs[b][p][n][1], dirs[b][p][n][2]), b, p }); - dirs[b][p].erase (dirs[b][p].begin()+n); + auto push = [&](size_t b, size_t p, size_t n) { + merged.push_back({Direction(dirs[b][p][n][0], dirs[b][p][n][1], dirs[b][p][n][2]), b, p}); + dirs[b][p].erase(dirs[b][p].begin() + n); }; - auto energy_pair = [&](const Direction& a, const Direction& b) - { + auto energy_pair = [&](const Direction &a, const Direction &b) { // use combination of mono- and bi-polar electrostatic repulsion models // to ensure adequate coverage of eddy-current space as well as // orientation space. // By default, use a moderate bias, favouring the bipolar model. - return (unipolar_weight+bipolar_weight) / (b-a).norm() - + bipolar_weight / (b+a).norm(); + return (unipolar_weight + bipolar_weight) / (b - a).norm() + bipolar_weight / (b + a).norm(); }; - auto energy = [&](size_t b, size_t p, size_t n) - { + auto energy = [&](size_t b, size_t p, size_t n) { value_type E = 0.0; - for (auto& d : merged) + for (auto &d : merged) if (d.b == b) - E += energy_pair (d.d, dirs[b][p][n]); + E += energy_pair(d.d, dirs[b][p][n]); return E; }; - auto find_lowest_energy_direction = [&](size_t b, size_t p) - { + auto find_lowest_energy_direction = [&](size_t b, size_t p) { size_t best = 0; value_type bestE = std::numeric_limits::max(); for (size_t n = 0; n < dirs[b][p].size(); ++n) { - value_type E = energy (b, p, n); + value_type E = energy(b, p, n); if (E < bestE) { bestE = E; best = n; @@ -151,38 +149,34 @@ void run () return best; }; - - vector fraction; - for (auto& d : dirs) { + for (auto &d : dirs) { size_t n = 0; - for (auto& m : d) + for (auto &m : d) n += m.size(); - fraction.push_back (float (n) / float (total)); + fraction.push_back(float(n) / float(total)); }; - push (0, 0, first); + push(0, 0, first); - vector counts (bvalue.size(), 0); + vector counts(bvalue.size(), 0); ++counts[0]; auto num_for_b = [&](size_t b) { size_t n = 0; - for (auto& d : merged) + for (auto &d : merged) if (d.b == b) ++n; return n; }; - - size_t nPE = num_subsets > 1 ? 1 : 0; while (merged.size() < total) { // find shell with shortfall in numbers: size_t b = 0, n; value_type fraction_diff = std::numeric_limits::max(); for (n = 0; n < bvalue.size(); ++n) { - value_type f_diff = float(num_for_b(n)) / float (merged.size()) - fraction[n]; + value_type f_diff = float(num_for_b(n)) / float(merged.size()) - fraction[n]; if (f_diff < fraction_diff && dirs[n][nPE].size()) { fraction_diff = f_diff; b = n; @@ -190,11 +184,12 @@ void run () } // find most distant direction for that shell & in the current PE direction: - n = find_lowest_energy_direction (b, nPE); + n = find_lowest_energy_direction(b, nPE); if (dirs[b][nPE].size()) - push (b, nPE, n); + push(b, nPE, n); else - WARN ("no directions remaining in b=" + str (bvalue[b]) + " shell for PE direction " + str(n) + " - PE directions will not cycle through perfectly"); + WARN("no directions remaining in b=" + str(bvalue[b]) + " shell for PE direction " + str(n) + + " - PE directions will not cycle through perfectly"); // update PE direction ++nPE; @@ -202,19 +197,14 @@ void run () nPE = 0; } - - - - // write-out: - File::OFStream out (argument[argument.size()-1]); - for (auto& d : merged) - out << MR::printf (num_subsets > 1 ? "%#20.15f %#20.15f %#20.15f %5d %3d\n" : "%#20.15f %#20.15f %#20.15f %5d\n", - d.d[0], d.d[1], d.d[2], - int (bvalue[d.b]), int (d.pe+1)); - + File::OFStream out(argument[argument.size() - 1]); + for (auto &d : merged) + out << MR::printf(num_subsets > 1 ? "%#20.15f %#20.15f %#20.15f %5d %3d\n" : "%#20.15f %#20.15f %#20.15f %5d\n", + d.d[0], + d.d[1], + d.d[2], + int(bvalue[d.b]), + int(d.pe + 1)); } - - - diff --git a/cmd/dirorder.cpp b/cmd/dirorder.cpp index 294d18f362..2fab3cdb37 100644 --- a/cmd/dirorder.cpp +++ b/cmd/dirorder.cpp @@ -15,50 +15,44 @@ */ #include "command.h" -#include "progressbar.h" -#include "math/rng.h" -#include "math/SH.h" -#include "dwi/gradient.h" #include "dwi/directions/file.h" +#include "dwi/gradient.h" +#include "math/SH.h" +#include "math/rng.h" +#include "progressbar.h" -#include #include +#include using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Reorder a set of directions to ensure near-uniformity upon truncation"; DESCRIPTION - + "The intent of this command is to reorder a set of gradient directions such that " - "if a scan is terminated prematurely, at any point, the acquired directions will " - "still be close to optimally distributed on the half-sphere."; + +"The intent of this command is to reorder a set of gradient directions such that " + "if a scan is terminated prematurely, at any point, the acquired directions will " + "still be close to optimally distributed on the half-sphere."; ARGUMENTS - + Argument ("input", "the input directions file").type_file_in() - + Argument ("output", "the output directions file").type_file_out(); + +Argument("input", "the input directions file").type_file_in() + + Argument("output", "the output directions file").type_file_out(); OPTIONS - + Option ("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); + +Option("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); } - - using value_type = double; - - -vector optimise (const Eigen::MatrixXd& directions, const size_t first_volume) -{ - vector indices (1, first_volume); +vector optimise(const Eigen::MatrixXd &directions, const size_t first_volume) { + vector indices(1, first_volume); vector remaining; for (size_t n = 0; n < size_t(directions.rows()); ++n) if (n != indices[0]) - remaining.push_back (n); + remaining.push_back(n); while (remaining.size()) { ssize_t best = 0; @@ -78,60 +72,51 @@ vector optimise (const Eigen::MatrixXd& directions, const size_t first_v } } - indices.push_back (remaining[best]); - remaining.erase (remaining.begin()+best); + indices.push_back(remaining[best]); + remaining.erase(remaining.begin() + best); } return indices; } - - - -value_type calc_cost (const Eigen::MatrixXd& directions, const vector& order) -{ - const size_t start = Math::SH::NforL (2); +value_type calc_cost(const Eigen::MatrixXd &directions, const vector &order) { + const size_t start = Math::SH::NforL(2); if (size_t(directions.rows()) <= start) return value_type(0); - Eigen::MatrixXd subset (start, 3); + Eigen::MatrixXd subset(start, 3); for (size_t i = 0; i != start; ++i) subset.row(i) = directions.row(order[i]); value_type cost = value_type(0); - for (size_t N = start+1; N < size_t(directions.rows()); ++N) { + for (size_t N = start + 1; N < size_t(directions.rows()); ++N) { // Don't include condition numbers where precisely the number of coefficients // for that spherical harmonic degree are included, as these tend to // be outliers - const size_t lmax = Math::SH::LforN (N-1); - subset.conservativeResize (N, 3); - subset.row(N-1) = directions.row(order[N-1]); - const value_type cond = DWI::condition_number_for_lmax (subset, lmax); + const size_t lmax = Math::SH::LforN(N - 1); + subset.conservativeResize(N, 3); + subset.row(N - 1) = directions.row(order[N - 1]); + const value_type cond = DWI::condition_number_for_lmax(subset, lmax); cost += cond; } return cost; } - - - -void run () -{ - auto directions = DWI::Directions::load_cartesian (argument[0]); +void run() { + auto directions = DWI::Directions::load_cartesian(argument[0]); size_t last_candidate_first_volume = directions.rows(); - if (size_t(directions.rows()) <= Math::SH::NforL (2)) { - WARN ("Very few directions in input (" - + str(directions.rows()) - + "); selection of first direction cannot be optimised" - + " (first direction in input will be first direction in output)"); + if (size_t(directions.rows()) <= Math::SH::NforL(2)) { + WARN("Very few directions in input (" + str(directions.rows()) + + "); selection of first direction cannot be optimised" + + " (first direction in input will be first direction in output)"); last_candidate_first_volume = 1; } value_type min_cost = std::numeric_limits::infinity(); vector best_order; - ProgressBar progress ("Determining best reordering", directions.rows()); + ProgressBar progress("Determining best reordering", directions.rows()); for (size_t first_volume = 0; first_volume != last_candidate_first_volume; ++first_volume) { - const vector order = optimise (directions, first_volume); - const value_type cost = calc_cost (directions, order); + const vector order = optimise(directions, first_volume); + const value_type cost = calc_cost(directions, order); if (cost < min_cost) { min_cost = cost; best_order = order; @@ -139,14 +124,9 @@ void run () ++progress; } - decltype(directions) output (directions.rows(), 3); + decltype(directions) output(directions.rows(), 3); for (ssize_t n = 0; n < directions.rows(); ++n) - output.row(n) = directions.row (best_order[n]); + output.row(n) = directions.row(best_order[n]); - DWI::Directions::save (output, argument[1], get_options("cartesian").size()); + DWI::Directions::save(output, argument[1], get_options("cartesian").size()); } - - - - - diff --git a/cmd/dirsplit.cpp b/cmd/dirsplit.cpp index 8f4af3dc5d..1b96d475f9 100644 --- a/cmd/dirsplit.cpp +++ b/cmd/dirsplit.cpp @@ -15,179 +15,157 @@ */ #include "command.h" -#include "progressbar.h" +#include "dwi/directions/file.h" #include "math/rng.h" +#include "progressbar.h" #include "thread.h" -#include "dwi/directions/file.h" constexpr size_t default_permutations = 1e8; - using namespace MR; using namespace App; -void usage () -{ -AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; +void usage() { + AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; -SYNOPSIS = "Split a set of evenly distributed directions (as generated " - "by dirgen) into approximately uniformly distributed subsets"; + SYNOPSIS = "Split a set of evenly distributed directions (as generated " + "by dirgen) into approximately uniformly distributed subsets"; -ARGUMENTS - + Argument ("dirs", "the text file containing the directions.").type_file_in() - + Argument ("out", "the output partitioned directions").type_file_out().allow_multiple(); + ARGUMENTS + +Argument("dirs", "the text file containing the directions.").type_file_in() + + Argument("out", "the output partitioned directions").type_file_out().allow_multiple(); + OPTIONS + +Option("permutations", "number of permutations to try (default: " + str(default_permutations) + ")") + + Argument("num").type_integer(1) -OPTIONS - + Option ("permutations", "number of permutations to try (default: " + str(default_permutations) + ")") - + Argument ("num").type_integer (1) - - + Option ("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); + + Option("cartesian", "Output the directions in Cartesian coordinates [x y z] instead of [az el]."); } using value_type = double; using vector3_type = Eigen::Vector3d; - -class Shared { - public: - Shared (const Eigen::MatrixXd& directions, size_t num_subsets, size_t target_num_permutations) : - directions (directions), subset (num_subsets), - best_energy (std::numeric_limits::max()), - target_num_permutations (target_num_permutations), - num_permutations (0) { - size_t s = 0; - for (ssize_t n = 0; n < directions.rows(); ++n) { - subset[s++].push_back (n); - if (s >= num_subsets) s = 0; - } - INFO ("split " + str(directions.rows()) + " directions into subsets with " + - str([&]{ vector c; for (auto& x : subset) c.push_back (x.size()); return c; }()) + " volumes"); - } - - - - - bool update (value_type energy, const vector>& set) - { - std::lock_guard lock (mutex); - if (!progress) progress.reset (new ProgressBar ("distributing directions", target_num_permutations)); - if (energy < best_energy) { - best_energy = energy; - best_subset = set; - progress->set_text ("distributing directions (current best configuration: energy = " + str(best_energy) + ")"); - } - ++num_permutations; - ++(*progress); - return num_permutations < target_num_permutations; +class Shared { +public: + Shared(const Eigen::MatrixXd &directions, size_t num_subsets, size_t target_num_permutations) + : directions(directions), + subset(num_subsets), + best_energy(std::numeric_limits::max()), + target_num_permutations(target_num_permutations), + num_permutations(0) { + size_t s = 0; + for (ssize_t n = 0; n < directions.rows(); ++n) { + subset[s++].push_back(n); + if (s >= num_subsets) + s = 0; } + INFO("split " + str(directions.rows()) + " directions into subsets with " + str([&] { + vector c; + for (auto &x : subset) + c.push_back(x.size()); + return c; + }()) + + " volumes"); + } - - - value_type energy (size_t i, size_t j) const { - vector3_type a = { directions(i,0), directions(i,1), directions(i,2) }; - vector3_type b = { directions(j,0), directions(j,1), directions(j,2) }; - return 1.0 / (a-b).norm() + 1.0 / (a+b).norm(); + bool update(value_type energy, const vector> &set) { + std::lock_guard lock(mutex); + if (!progress) + progress.reset(new ProgressBar("distributing directions", target_num_permutations)); + if (energy < best_energy) { + best_energy = energy; + best_subset = set; + progress->set_text("distributing directions (current best configuration: energy = " + str(best_energy) + ")"); } + ++num_permutations; + ++(*progress); + return num_permutations < target_num_permutations; + } + value_type energy(size_t i, size_t j) const { + vector3_type a = {directions(i, 0), directions(i, 1), directions(i, 2)}; + vector3_type b = {directions(j, 0), directions(j, 1), directions(j, 2)}; + return 1.0 / (a - b).norm() + 1.0 / (a + b).norm(); + } - const vector>& get_init_subset () const { return subset; } - const vector>& get_best_subset () const { return best_subset; } - - - protected: - const Eigen::MatrixXd& directions; - std::mutex mutex; - vector> subset, best_subset; - value_type best_energy; - const size_t target_num_permutations; - size_t num_permutations; - std::unique_ptr progress; + const vector> &get_init_subset() const { return subset; } + const vector> &get_best_subset() const { return best_subset; } + +protected: + const Eigen::MatrixXd &directions; + std::mutex mutex; + vector> subset, best_subset; + value_type best_energy; + const size_t target_num_permutations; + size_t num_permutations; + std::unique_ptr progress; }; +class EnergyCalculator { +public: + EnergyCalculator(Shared &shared) : shared(shared), subset(shared.get_init_subset()) {} + void execute() { + while (eval()) + ; + } + void next_permutation() { + size_t i, j; + std::uniform_int_distribution dist(0, subset.size() - 1); + do { + i = dist(rng); + j = dist(rng); + } while (i == j); + size_t n_i = std::uniform_int_distribution(0, subset[i].size() - 1)(rng); + size_t n_j = std::uniform_int_distribution(0, subset[j].size() - 1)(rng); + std::swap(subset[i][n_i], subset[j][n_j]); + } + bool eval() { + next_permutation(); -class EnergyCalculator { - public: - EnergyCalculator (Shared& shared) : shared (shared), subset (shared.get_init_subset()) { } - - void execute () { - while (eval()); - } - - - void next_permutation () - { - size_t i,j; - std::uniform_int_distribution dist(0, subset.size()-1); - do { - i = dist (rng); - j = dist (rng); - } while (i == j); - - size_t n_i = std::uniform_int_distribution (0, subset[i].size()-1) (rng); - size_t n_j = std::uniform_int_distribution (0, subset[j].size()-1) (rng); - - std::swap (subset[i][n_i], subset[j][n_j]); + value_type energy = 0.0; + for (auto &s : subset) { + value_type current_energy = 0.0; + for (size_t i = 0; i < s.size(); ++i) + for (size_t j = i + 1; j < s.size(); ++j) + current_energy += shared.energy(s[i], s[j]); + energy = std::max(energy, current_energy); } - bool eval () - { - next_permutation(); - - value_type energy = 0.0; - for (auto& s: subset) { - value_type current_energy = 0.0; - for (size_t i = 0; i < s.size(); ++i) - for (size_t j = i+1; j < s.size(); ++j) - current_energy += shared.energy (s[i], s[j]); - energy = std::max (energy, current_energy); - } - - return shared.update (energy, subset); - } + return shared.update(energy, subset); + } - protected: - Shared& shared; - vector> subset; - Math::RNG rng; +protected: + Shared &shared; + vector> subset; + Math::RNG rng; }; - - - - - - -void run () -{ - auto directions = DWI::Directions::load_cartesian (argument[0]); +void run() { + auto directions = DWI::Directions::load_cartesian(argument[0]); const size_t num_subsets = argument.size() - 1; if (num_subsets == 1) - throw Exception ("Directions must be split across two or more output files"); + throw Exception("Directions must be split across two or more output files"); - const size_t num_permutations = get_option_value ("permutations", default_permutations); + const size_t num_permutations = get_option_value("permutations", default_permutations); vector> best; { - Shared shared (directions, num_subsets, num_permutations); - Thread::run (Thread::multi (EnergyCalculator (shared)), "energy eval thread"); + Shared shared(directions, num_subsets, num_permutations); + Thread::run(Thread::multi(EnergyCalculator(shared)), "energy eval thread"); best = shared.get_best_subset(); } const bool cartesian = get_options("cartesian").size(); for (size_t i = 0; i < best.size(); ++i) { - Eigen::MatrixXd output (best[i].size(), 3); + Eigen::MatrixXd output(best[i].size(), 3); for (size_t n = 0; n < best[i].size(); ++n) - output.row(n) = directions.row (best[i][n]); - DWI::Directions::save (output, argument[i+1], cartesian); + output.row(n) = directions.row(best[i][n]); + DWI::Directions::save(output, argument[i + 1], cartesian); } - } - - diff --git a/cmd/dirstat.cpp b/cmd/dirstat.cpp index 0a765b7d20..0190a0bc71 100644 --- a/cmd/dirstat.cpp +++ b/cmd/dirstat.cpp @@ -15,207 +15,172 @@ */ #include "command.h" -#include "progressbar.h" -#include "header.h" #include "dwi/gradient.h" #include "dwi/shells.h" #include "file/matrix.h" +#include "header.h" #include "math/sphere.h" +#include "progressbar.h" #include "dwi/directions/file.h" - - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Report statistics on a direction set"; DESCRIPTION - + "This command will accept as inputs:" - + "- directions file in spherical coordinates (ASCII text, [ az el ] space-separated values, one per line);" - + "- directions file in Cartesian coordinates (ASCII text, [ x y z ] space-separated values, one per line);" - + "- DW gradient files (MRtrix format: ASCII text, [ x y z b ] space-separated values, one per line);" - + "- image files, using the DW gradient scheme found in the header (or provided using the appropriate command line options below)." - - + "By default, this produces all relevant metrics for the direction set " - "provided. If the direction set contains multiple shells, metrics are " - "provided for each shell separately." - - + "Metrics are produced assuming a unipolar or bipolar electrostatic " - "repulsion model, producing the potential energy (total, mean, min & max), " - "and the nearest-neighbour angles (mean, min & max). The condition " - "number is also produced for the spherical harmonic fits up to the highest " - "harmonic order supported by the number of volumes. Finally, the norm of the " - "mean direction vector is provided as a measure of the overall symmetry of " - "the direction set (important with respect to eddy-current resilience)." - - + "Specific metrics can also be queried independently via the \"-output\" " - "option, using these shorthands: \n" - "U/B for unipolar/bipolar model, \n" - "E/N for energy and nearest-neighbour respectively, \n" - "t/-/+ for total/min/max respectively (mean implied otherwise); \n" - "SHn for condition number of SH fit at order n (with n an even integer); \n" - "ASYM for asymmetry index (norm of mean direction vector); \n" - "N for the number of directions."; + +"This command will accept as inputs:" + + "- directions file in spherical coordinates (ASCII text, [ az el ] space-separated values, one per line);" + + "- directions file in Cartesian coordinates (ASCII text, [ x y z ] space-separated values, one per line);" + + "- DW gradient files (MRtrix format: ASCII text, [ x y z b ] space-separated values, one per line);" + + "- image files, using the DW gradient scheme found in the header (or provided using the appropriate command line " + "options below)." + + + "By default, this produces all relevant metrics for the direction set " + "provided. If the direction set contains multiple shells, metrics are " + "provided for each shell separately." + + + "Metrics are produced assuming a unipolar or bipolar electrostatic " + "repulsion model, producing the potential energy (total, mean, min & max), " + "and the nearest-neighbour angles (mean, min & max). The condition " + "number is also produced for the spherical harmonic fits up to the highest " + "harmonic order supported by the number of volumes. Finally, the norm of the " + "mean direction vector is provided as a measure of the overall symmetry of " + "the direction set (important with respect to eddy-current resilience)." + + + "Specific metrics can also be queried independently via the \"-output\" " + "option, using these shorthands: \n" + "U/B for unipolar/bipolar model, \n" + "E/N for energy and nearest-neighbour respectively, \n" + "t/-/+ for total/min/max respectively (mean implied otherwise); \n" + "SHn for condition number of SH fit at order n (with n an even integer); \n" + "ASYM for asymmetry index (norm of mean direction vector); \n" + "N for the number of directions."; EXAMPLES - + Example ("Default usage", - "dirstat directions.txt", - "This provides a pretty-printed list of all metrics available.") + +Example("Default usage", "dirstat directions.txt", "This provides a pretty-printed list of all metrics available.") - + Example ("Write a single metric of interest to standard output", - "dirstat grad.b -shell 3000 -output SH8", - "requests the condition number of SH fit of b=3000 shell " - "directions at SH order 8") + + Example("Write a single metric of interest to standard output", + "dirstat grad.b -shell 3000 -output SH8", + "requests the condition number of SH fit of b=3000 shell " + "directions at SH order 8") - + Example ("Write multiple metrics of interest to standard output", - "dirstat dwi.mif -output BN,BN-,BN+", - "requests the mean, min and max nearest-neighour " - "angles assuming a bipolar model."); + + Example("Write multiple metrics of interest to standard output", + "dirstat dwi.mif -output BN,BN-,BN+", + "requests the mean, min and max nearest-neighour " + "angles assuming a bipolar model."); ARGUMENTS - + Argument ("dirs", "the text file or image containing the directions.").type_file_in(); + +Argument("dirs", "the text file or image containing the directions.").type_file_in(); OPTIONS - + Option ("output", "output selected metrics as a space-delimited list, " - "suitable for use in scripts. This will produce one line of values per " - "selected shell. Valid metrics are as specified in the description " - "above.") - + Argument ("list") - + DWI::ShellsOption - + DWI::GradImportOptions(); + +Option("output", + "output selected metrics as a space-delimited list, " + "suitable for use in scripts. This will produce one line of values per " + "selected shell. Valid metrics are as specified in the description " + "above.") + + Argument("list") + DWI::ShellsOption + DWI::GradImportOptions(); } - - int precision = 6; -void report (const std::string& title, Eigen::MatrixXd& directions); - +void report(const std::string &title, Eigen::MatrixXd &directions); - -void run () -{ +void run() { Eigen::MatrixXd directions; try { - directions = DWI::Directions::load_cartesian (argument[0]); - } - catch (Exception& E) { + directions = DWI::Directions::load_cartesian(argument[0]); + } catch (Exception &E) { try { - directions = File::Matrix::load_matrix (argument[0]); - } - catch (Exception& E) { - auto header = Header::open (argument[0]); - directions = DWI::get_DW_scheme (header); + directions = File::Matrix::load_matrix(argument[0]); + } catch (Exception &E) { + auto header = Header::open(argument[0]); + directions = DWI::get_DW_scheme(header); } } if (directions.cols() >= 4) { int n_start = 0; - auto shells = DWI::Shells (directions).select_shells (false, false, false); - if (get_options ("shells").empty() && shells.has_bzero() && shells.count() > 1) { + auto shells = DWI::Shells(directions).select_shells(false, false, false); + if (get_options("shells").empty() && shells.has_bzero() && shells.count() > 1) { n_start = 1; if (get_options("output").empty()) - print (std::string (argument[0]) + " (b=0) [ " + str(shells.smallest().count(), precision) + " volumes ]\n\n"); + print(std::string(argument[0]) + " (b=0) [ " + str(shells.smallest().count(), precision) + " volumes ]\n\n"); } - Eigen::MatrixXd dirs; for (size_t n = n_start; n < shells.count(); ++n) { - dirs.resize (shells[n].count(), 3); + dirs.resize(shells[n].count(), 3); for (size_t idx = 0; idx < shells[n].count(); ++idx) - dirs.row (idx) = directions.row (shells[n].get_volumes()[idx]).head (3); + dirs.row(idx) = directions.row(shells[n].get_volumes()[idx]).head(3); - report (std::string (argument[0]) + " (b=" + str(shells[n].get_mean()) + ")", dirs); + report(std::string(argument[0]) + " (b=" + str(shells[n].get_mean()) + ")", dirs); } - } - else - report (argument[0], directions); + } else + report(argument[0], directions); } - - - - -vector summarise_NN (const vector& NN) -{ +vector summarise_NN(const vector &NN) { double NN_min = std::numeric_limits::max(); double NN_mean = 0.0; double NN_max = 0.0; for (auto a : NN) { - a = (180.0/Math::pi) * std::acos (a); + a = (180.0 / Math::pi) * std::acos(a); NN_mean += a; - NN_min = std::min (NN_min, a); - NN_max = std::max (NN_max, a); + NN_min = std::min(NN_min, a); + NN_max = std::max(NN_max, a); } NN_mean /= NN.size(); - return { NN_mean, NN_min, NN_max }; + return {NN_mean, NN_min, NN_max}; } - - - - - - -vector summarise_E (const vector& E) -{ +vector summarise_E(const vector &E) { double E_min = std::numeric_limits::max(); double E_total = 0.0; double E_max = 0.0; for (auto e : E) { E_total += e; - E_min = std::min (E_min, e); - E_max = std::max (E_max, e); + E_min = std::min(E_min, e); + E_max = std::max(E_max, e); } - return { 0.5*E_total, E_total/E.size(), E_min, E_max }; + return {0.5 * E_total, E_total / E.size(), E_min, E_max}; } - - -class Metrics -{ - public: - vector BN, UN, BE, UE, SH; - default_type ASYM; - size_t ndirs; +class Metrics { +public: + vector BN, UN, BE, UE, SH; + default_type ASYM; + size_t ndirs; }; - - - - - -Metrics compute (Eigen::MatrixXd& directions) -{ +Metrics compute(Eigen::MatrixXd &directions) { if (directions.cols() < 3) - throw Exception ("unexpected matrix size for scheme \"" + str(argument[0]) + "\""); - Math::Sphere::normalise_cartesian (directions); + throw Exception("unexpected matrix size for scheme \"" + str(argument[0]) + "\""); + Math::Sphere::normalise_cartesian(directions); - vector NN_bipolar (directions.rows(), -1.0); - vector NN_unipolar (directions.rows(), -1.0); + vector NN_bipolar(directions.rows(), -1.0); + vector NN_unipolar(directions.rows(), -1.0); - vector E_bipolar (directions.rows(), 0.0); - vector E_unipolar (directions.rows(), 0.0); + vector E_bipolar(directions.rows(), 0.0); + vector E_unipolar(directions.rows(), 0.0); - for (ssize_t i = 0; i < directions.rows()-1; ++i) { - for (ssize_t j = i+1; j < directions.rows(); ++j) { - double cos_angle = directions.row(i).head(3).normalized().dot (directions.row(j).head(3).normalized()); - NN_unipolar[i] = std::max (NN_unipolar[i], cos_angle); - NN_unipolar[j] = std::max (NN_unipolar[j], cos_angle); - cos_angle = abs (cos_angle); - NN_bipolar[i] = std::max (NN_bipolar[i], cos_angle); - NN_bipolar[j] = std::max (NN_bipolar[j], cos_angle); + for (ssize_t i = 0; i < directions.rows() - 1; ++i) { + for (ssize_t j = i + 1; j < directions.rows(); ++j) { + double cos_angle = directions.row(i).head(3).normalized().dot(directions.row(j).head(3).normalized()); + NN_unipolar[i] = std::max(NN_unipolar[i], cos_angle); + NN_unipolar[j] = std::max(NN_unipolar[j], cos_angle); + cos_angle = abs(cos_angle); + NN_bipolar[i] = std::max(NN_bipolar[i], cos_angle); + NN_bipolar[j] = std::max(NN_bipolar[j], cos_angle); double E = 1.0 / (directions.row(i).head(3) - directions.row(j).head(3)).norm(); @@ -226,101 +191,111 @@ Metrics compute (Eigen::MatrixXd& directions) E_bipolar[i] += E; E_bipolar[j] += E; - } } Metrics metrics; metrics.ndirs = directions.rows(); - metrics.UN = summarise_NN (NN_unipolar); - metrics.BN = summarise_NN (NN_bipolar); - metrics.UE = summarise_E (E_unipolar); - metrics.BE = summarise_E (E_bipolar); + metrics.UN = summarise_NN(NN_unipolar); + metrics.BN = summarise_NN(NN_bipolar); + metrics.UE = summarise_E(E_unipolar); + metrics.BE = summarise_E(E_bipolar); - for (size_t lmax = 2; lmax <= Math::SH::LforN (directions.rows()); lmax += 2) - metrics.SH.push_back (DWI::condition_number_for_lmax (directions, lmax)); + for (size_t lmax = 2; lmax <= Math::SH::LforN(directions.rows()); lmax += 2) + metrics.SH.push_back(DWI::condition_number_for_lmax(directions, lmax)); metrics.ASYM = directions.leftCols(3).colwise().mean().norm(); return metrics; } +void output_selected(const Metrics &metrics, const std::string &selection) { + auto select = split(selection, ", \t\n", true); - - -void output_selected (const Metrics& metrics, const std::string& selection) -{ - auto select = split (selection, ", \t\n", true); - - for (const auto& x : select) { + for (const auto &x : select) { const auto xl = lowercase(x); - if (xl == "uet") std::cout << metrics.UE[0] << " "; - else if (xl == "ue") std::cout << metrics.UE[1] << " "; - else if (xl == "ue-") std::cout << metrics.UE[2] << " "; - else if (xl == "ue+") std::cout << metrics.UE[3] << " "; - else if (xl == "bet") std::cout << metrics.BE[0] << " "; - else if (xl == "be") std::cout << metrics.BE[1] << " "; - else if (xl == "be-") std::cout << metrics.BE[2] << " "; - else if (xl == "be+") std::cout << metrics.BE[3] << " "; - else if (xl == "un") std::cout << metrics.UN[0] << " "; - else if (xl == "un-") std::cout << metrics.UN[1] << " "; - else if (xl == "un+") std::cout << metrics.UN[2] << " "; - else if (xl == "bn") std::cout << metrics.BN[0] << " "; - else if (xl == "bn-") std::cout << metrics.BN[1] << " "; - else if (xl == "bn+") std::cout << metrics.BN[2] << " "; - else if (xl == "asym") std::cout << metrics.ASYM << " "; - else if (xl == "n") std::cout << metrics.ndirs << " "; - else if (xl.substr(0,2) == "sh") { + if (xl == "uet") + std::cout << metrics.UE[0] << " "; + else if (xl == "ue") + std::cout << metrics.UE[1] << " "; + else if (xl == "ue-") + std::cout << metrics.UE[2] << " "; + else if (xl == "ue+") + std::cout << metrics.UE[3] << " "; + else if (xl == "bet") + std::cout << metrics.BE[0] << " "; + else if (xl == "be") + std::cout << metrics.BE[1] << " "; + else if (xl == "be-") + std::cout << metrics.BE[2] << " "; + else if (xl == "be+") + std::cout << metrics.BE[3] << " "; + else if (xl == "un") + std::cout << metrics.UN[0] << " "; + else if (xl == "un-") + std::cout << metrics.UN[1] << " "; + else if (xl == "un+") + std::cout << metrics.UN[2] << " "; + else if (xl == "bn") + std::cout << metrics.BN[0] << " "; + else if (xl == "bn-") + std::cout << metrics.BN[1] << " "; + else if (xl == "bn+") + std::cout << metrics.BN[2] << " "; + else if (xl == "asym") + std::cout << metrics.ASYM << " "; + else if (xl == "n") + std::cout << metrics.ndirs << " "; + else if (xl.substr(0, 2) == "sh") { size_t order = to(x.substr(2)); if (order & 1U || order < 2) - throw Exception ("spherical harmonic order must be an even positive integer"); - order = (order/2)-1; + throw Exception("spherical harmonic order must be an even positive integer"); + order = (order / 2) - 1; if (order >= metrics.SH.size()) - throw Exception ("spherical harmonic order requested is too large given number of directions"); + throw Exception("spherical harmonic order requested is too large given number of directions"); std::cout << metrics.SH[order] << " "; - } - else - throw Exception ("unknown output specifier \"" + x + "\""); + } else + throw Exception("unknown output specifier \"" + x + "\""); } std::cout << "\n"; } +void report(const std::string &title, Eigen::MatrixXd &directions) { + auto metrics = compute(directions); - -void report (const std::string& title, Eigen::MatrixXd& directions) -{ - auto metrics = compute (directions); - - auto opt = get_options ("output"); + auto opt = get_options("output"); if (opt.size()) { - output_selected (metrics, opt[0][0]); + output_selected(metrics, opt[0][0]); return; } std::string output = title + " [ " + str(metrics.ndirs, precision) + " directions ]\n"; output += "\n Bipolar electrostatic repulsion model:\n"; - output += " nearest-neighbour angles: mean = " + str(metrics.BN[0], precision) + ", range [ " + str(metrics.BN[1], precision) + " - " + str(metrics.BN[2], precision) + " ]\n"; - output += " energy: total = " + str(metrics.BE[0], precision) + ", mean = " + str(metrics.BE[1], precision) + ", range [ " + str(metrics.BE[2], precision) + " - " + str(metrics.BE[3], precision) + " ]\n"; + output += " nearest-neighbour angles: mean = " + str(metrics.BN[0], precision) + ", range [ " + + str(metrics.BN[1], precision) + " - " + str(metrics.BN[2], precision) + " ]\n"; + output += " energy: total = " + str(metrics.BE[0], precision) + ", mean = " + str(metrics.BE[1], precision) + + ", range [ " + str(metrics.BE[2], precision) + " - " + str(metrics.BE[3], precision) + " ]\n"; output += "\n Unipolar electrostatic repulsion model:\n"; - output += " nearest-neighbour angles: mean = " + str(metrics.UN[0], precision) + ", range [ " + str(metrics.UN[1], precision) + " - " + str(metrics.UN[2], precision) + " ]\n"; - output += " energy: total = " + str(metrics.UE[0], precision) + ", mean = " + str(metrics.UE[1], precision) + ", range [ " + str(metrics.UE[2], precision) + " - " + str(metrics.UE[3], precision) + " ]\n"; - + output += " nearest-neighbour angles: mean = " + str(metrics.UN[0], precision) + ", range [ " + + str(metrics.UN[1], precision) + " - " + str(metrics.UN[2], precision) + " ]\n"; + output += " energy: total = " + str(metrics.UE[0], precision) + ", mean = " + str(metrics.UE[1], precision) + + ", range [ " + str(metrics.UE[2], precision) + " - " + str(metrics.UE[3], precision) + " ]\n"; output += "\n Spherical Harmonic fit:\n"; if (metrics.SH.size() > 1) - output += " condition numbers for lmax = 2 -> " + str(metrics.SH.size()*2) + ": " + str(metrics.SH, precision) + "\n"; + output += " condition numbers for lmax = 2 -> " + str(metrics.SH.size() * 2) + ": " + + str(metrics.SH, precision) + "\n"; else output += " condition number for lmax = 2: " + str(metrics.SH[0], precision) + "\n"; output += "\n Asymmetry of sampling:\n norm of mean direction vector = " + str(metrics.ASYM, precision) + "\n"; if (metrics.ASYM >= 0.1) - output += std::string(" WARNING: sampling is ") + ( metrics.ASYM >= 0.4 ? "strongly" : "moderately" ) - + " asymmetric - this may affect resiliance to eddy-current distortions\n"; + output += std::string(" WARNING: sampling is ") + (metrics.ASYM >= 0.4 ? "strongly" : "moderately") + + " asymmetric - this may affect resiliance to eddy-current distortions\n"; output += "\n"; - print (output); + print(output); } - diff --git a/cmd/dwi2adc.cpp b/cmd/dwi2adc.cpp index 19aa59621a..9a3e0d84ea 100644 --- a/cmd/dwi2adc.cpp +++ b/cmd/dwi2adc.cpp @@ -14,104 +14,81 @@ * For more details, see http://www.mrtrix.org/. */ +#include "algo/threaded_copy.h" #include "command.h" +#include "dwi/gradient.h" #include "image.h" +#include "math/least_squares.h" #include "phase_encoding.h" #include "progressbar.h" -#include "algo/threaded_copy.h" -#include "math/least_squares.h" -#include "dwi/gradient.h" - using namespace MR; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Convert mean dwi (trace-weighted) images to mean ADC maps"; ARGUMENTS - + Argument ("input", "the input image.").type_image_in () - + Argument ("output", "the output image.").type_image_out (); + +Argument("input", "the input image.").type_image_in() + Argument("output", "the output image.").type_image_out(); OPTIONS - + DWI::GradImportOptions(); + +DWI::GradImportOptions(); } - - using value_type = float; - - class DWI2ADC { - public: - DWI2ADC (const Eigen::MatrixXd& binv, size_t dwi_axis) : - dwi (binv.cols()), - adc (2), - binv (binv), - dwi_axis (dwi_axis) { } - DWI2ADC (const DWI2ADC& that) : - dwi (that.dwi.size()), - adc (that.adc.size()), - binv (that.binv), - dwi_axis (that.dwi_axis) {} - - template - void operator() (DWIType& dwi_image, ADCType& adc_image) { - for (auto l = Loop (dwi_axis) (dwi_image); l; ++l) { - value_type val = dwi_image.value(); - dwi[dwi_image.index (dwi_axis)] = val ? std::log (val) : 1.0e-12; - } - - adc = binv * dwi; - - adc_image.index(3) = 0; - adc_image.value() = std::exp (adc[0]); - adc_image.index(3) = 1; - adc_image.value() = adc[1]; - } - - protected: - Eigen::VectorXd dwi, adc; - const Eigen::MatrixXd& binv; - const size_t dwi_axis; -}; - - +public: + DWI2ADC(const Eigen::MatrixXd &binv, size_t dwi_axis) : dwi(binv.cols()), adc(2), binv(binv), dwi_axis(dwi_axis) {} + DWI2ADC(const DWI2ADC &that) : dwi(that.dwi.size()), adc(that.adc.size()), binv(that.binv), dwi_axis(that.dwi_axis) {} + + template void operator()(DWIType &dwi_image, ADCType &adc_image) { + for (auto l = Loop(dwi_axis)(dwi_image); l; ++l) { + value_type val = dwi_image.value(); + dwi[dwi_image.index(dwi_axis)] = val ? std::log(val) : 1.0e-12; + } + + adc = binv * dwi; + + adc_image.index(3) = 0; + adc_image.value() = std::exp(adc[0]); + adc_image.index(3) = 1; + adc_image.value() = adc[1]; + } +protected: + Eigen::VectorXd dwi, adc; + const Eigen::MatrixXd &binv; + const size_t dwi_axis; +}; -void run () { - auto dwi = Header::open (argument[0]).get_image(); - auto grad = DWI::get_DW_scheme (dwi); +void run() { + auto dwi = Header::open(argument[0]).get_image(); + auto grad = DWI::get_DW_scheme(dwi); size_t dwi_axis = 3; - while (dwi.size (dwi_axis) < 2) + while (dwi.size(dwi_axis) < 2) ++dwi_axis; - INFO ("assuming DW images are stored along axis " + str (dwi_axis)); + INFO("assuming DW images are stored along axis " + str(dwi_axis)); - Eigen::MatrixXd b (grad.rows(), 2); + Eigen::MatrixXd b(grad.rows(), 2); for (ssize_t i = 0; i < b.rows(); ++i) { - b(i,0) = 1.0; - b(i,1) = -grad (i,3); + b(i, 0) = 1.0; + b(i, 1) = -grad(i, 3); } - auto binv = Math::pinv (b); + auto binv = Math::pinv(b); - Header header (dwi); + Header header(dwi); header.datatype() = DataType::Float32; - DWI::stash_DW_scheme (header, grad); - PhaseEncoding::clear_scheme (header); + DWI::stash_DW_scheme(header, grad); + PhaseEncoding::clear_scheme(header); header.ndim() = 4; header.size(3) = 2; - auto adc = Image::create (argument[1], header); + auto adc = Image::create(argument[1], header); - ThreadedLoop ("computing ADC values", dwi, 0, 3) - .run (DWI2ADC (binv, dwi_axis), dwi, adc); + ThreadedLoop("computing ADC values", dwi, 0, 3).run(DWI2ADC(binv, dwi_axis), dwi, adc); } - - diff --git a/cmd/dwi2fod.cpp b/cmd/dwi2fod.cpp index 44869f3cf9..ffa3757a20 100644 --- a/cmd/dwi2fod.cpp +++ b/cmd/dwi2fod.cpp @@ -14,320 +14,293 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "header.h" -#include "image.h" -#include "phase_encoding.h" #include "algo/threaded_loop.h" +#include "command.h" #include "dwi/gradient.h" #include "dwi/shells.h" +#include "header.h" +#include "image.h" #include "math/SH.h" +#include "phase_encoding.h" #include "dwi/sdeconv/csd.h" #include "dwi/sdeconv/msmt_csd.h" - using namespace MR; using namespace App; +const char *const algorithms[] = {"csd", "msmt_csd", NULL}; -const char* const algorithms[] = { "csd", "msmt_csd", NULL }; +OptionGroup CommonOptions = OptionGroup("Options common to more than one algorithm") + + Option("directions", + "specify the directions over which to apply the non-negativity constraint " + "(by default, the built-in 300 direction set is used). These should be " + "supplied as a text file containing [ az el ] pairs for the directions.") + + Argument("file").type_file_in() + + Option("lmax", + "the maximum spherical harmonic order for the output FOD(s)." + "For algorithms with multiple outputs, this should be " + "provided as a comma-separated list of integers, one for " + "each output image; for single-output algorithms, only " + "a single integer should be provided. If omitted, the " + "command will use the lmax of the corresponding response " + "function (i.e based on its number of coefficients), " + "up to a maximum of 8.") + + Argument("order").type_sequence_int() -OptionGroup CommonOptions = OptionGroup ("Options common to more than one algorithm") + + Option("mask", "only perform computation within the specified binary brain mask image.") + + Argument("image").type_image_in(); - + Option ("directions", - "specify the directions over which to apply the non-negativity constraint " - "(by default, the built-in 300 direction set is used). These should be " - "supplied as a text file containing [ az el ] pairs for the directions.") - + Argument ("file").type_file_in() - - + Option ("lmax", - "the maximum spherical harmonic order for the output FOD(s)." - "For algorithms with multiple outputs, this should be " - "provided as a comma-separated list of integers, one for " - "each output image; for single-output algorithms, only " - "a single integer should be provided. If omitted, the " - "command will use the lmax of the corresponding response " - "function (i.e based on its number of coefficients), " - "up to a maximum of 8.") - + Argument ("order").type_sequence_int() - - + Option ("mask", - "only perform computation within the specified binary brain mask image.") - + Argument ("image").type_image_in(); - -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com) and Ben Jeurissen (ben.jeurissen@uantwerpen.be)"; SYNOPSIS = "Estimate fibre orientation distributions from diffusion data using spherical deconvolution"; DESCRIPTION - + Math::SH::encoding_description; + +Math::SH::encoding_description; EXAMPLES - + Example ("Perform single-shell single-tissue CSD", - "dwi2fod csd dwi.mif response_wm.txt wmfod.mif", - "This algorithm is designed for single-shell data and only uses a single " - "b-value. The response function text file provided should only contain a " - "a single row, corresponding to the b-value used for CSD.") - - + Example ("Perform multi-shell multi-tissue CSD", - "dwi2fod msmt_csd dwi.mif response_wm.txt wmfod.mif response_gm.txt gm.mif response_csf.txt csf.mif", - "This example is the most common use case of multi-tissue CSD, estimating " - "a white matter FOD, and grey matter and CSF compartments. This algorithm " - "requires at least three unique b-values to estimate three tissue compartments. " - "Each response function text file should have a number of rows equal to the " - "number of b-values used. If only two unique b-values are available, it's also " - "possible to estimate only two tissue compartments, e.g., white matter and CSF."); + +Example("Perform single-shell single-tissue CSD", + "dwi2fod csd dwi.mif response_wm.txt wmfod.mif", + "This algorithm is designed for single-shell data and only uses a single " + "b-value. The response function text file provided should only contain a " + "a single row, corresponding to the b-value used for CSD.") + + + Example("Perform multi-shell multi-tissue CSD", + "dwi2fod msmt_csd dwi.mif response_wm.txt wmfod.mif response_gm.txt gm.mif response_csf.txt csf.mif", + "This example is the most common use case of multi-tissue CSD, estimating " + "a white matter FOD, and grey matter and CSF compartments. This algorithm " + "requires at least three unique b-values to estimate three tissue compartments. " + "Each response function text file should have a number of rows equal to the " + "number of b-values used. If only two unique b-values are available, it's also " + "possible to estimate only two tissue compartments, e.g., white matter and CSF."); REFERENCES - + "* If using csd algorithm:\n" - "Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal - "Robust determination of the fibre orientation distribution in diffusion MRI: " - "Non-negativity constrained super-resolved spherical deconvolution. " - "NeuroImage, 2007, 35, 1459-1472" - - + "* If using msmt_csd algorithm:\n" - "Jeurissen, B; Tournier, J-D; Dhollander, T; Connelly, A & Sijbers, J. " // Internal - "Multi-tissue constrained spherical deconvolution for improved analysis of multi-shell diffusion MRI data. " - "NeuroImage, 2014, 103, 411-426" - - + "Tournier, J.-D.; Calamante, F., Gadian, D.G. & Connelly, A. " // Internal - "Direct estimation of the fiber orientation density function from " - "diffusion-weighted MRI data using spherical deconvolution. " - "NeuroImage, 2004, 23, 1176-1185"; + +"* If using csd algorithm:\n" + "Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal + "Robust determination of the fibre orientation distribution in diffusion MRI: " + "Non-negativity constrained super-resolved spherical deconvolution. " + "NeuroImage, 2007, 35, 1459-1472" + + + "* If using msmt_csd algorithm:\n" + "Jeurissen, B; Tournier, J-D; Dhollander, T; Connelly, A & Sijbers, J. " // Internal + "Multi-tissue constrained spherical deconvolution for improved analysis of multi-shell diffusion MRI data. " + "NeuroImage, 2014, 103, 411-426" + + + "Tournier, J.-D.; Calamante, F., Gadian, D.G. & Connelly, A. " // Internal + "Direct estimation of the fiber orientation density function from " + "diffusion-weighted MRI data using spherical deconvolution. " + "NeuroImage, 2004, 23, 1176-1185"; ARGUMENTS - + Argument ("algorithm", "the algorithm to use for FOD estimation. " - "(options are: " + join(algorithms, ",") + ")").type_choice (algorithms) - + Argument ("dwi", "the input diffusion-weighted image").type_image_in() - + Argument ("response odf", "pairs of input tissue response and output ODF images").allow_multiple(); + +Argument("algorithm", + "the algorithm to use for FOD estimation. " + "(options are: " + + join(algorithms, ",") + ")") + .type_choice(algorithms) + + Argument("dwi", "the input diffusion-weighted image").type_image_in() + + Argument("response odf", "pairs of input tissue response and output ODF images").allow_multiple(); OPTIONS - + DWI::GradImportOptions() - + DWI::ShellsOption - + CommonOptions - + DWI::SDeconv::CSD_options - + DWI::SDeconv::MSMT_CSD_options - + Stride::Options; + +DWI::GradImportOptions() + DWI::ShellsOption + CommonOptions + DWI::SDeconv::CSD_options + + DWI::SDeconv::MSMT_CSD_options + Stride::Options; } +class CSD_Processor { +public: + CSD_Processor(const DWI::SDeconv::CSD::Shared &shared, Image &mask) + : sdeconv(shared), data(shared.dwis.size()), mask(mask) {} - -class CSD_Processor { - public: - CSD_Processor (const DWI::SDeconv::CSD::Shared& shared, Image& mask) : - sdeconv (shared), - data (shared.dwis.size()), - mask (mask) { } - - - void operator () (Image& dwi, Image& fod) { - if (!load_data (dwi)) { - for (auto l = Loop (3) (fod); l; ++l) - fod.value() = 0.0; - return; - } - - sdeconv.set (data); - - size_t n; - for (n = 0; n < sdeconv.shared.niter; n++) - if (sdeconv.iterate()) - break; - - if (sdeconv.shared.niter && n >= sdeconv.shared.niter) - INFO ("voxel [ " + str (dwi.index(0)) + " " + str (dwi.index(1)) + " " + str (dwi.index(2)) + - " ] did not reach full convergence"); - - fod.row(3) = sdeconv.FOD(); + void operator()(Image &dwi, Image &fod) { + if (!load_data(dwi)) { + for (auto l = Loop(3)(fod); l; ++l) + fod.value() = 0.0; + return; } + sdeconv.set(data); - private: - DWI::SDeconv::CSD sdeconv; - Eigen::VectorXd data; - Image mask; + size_t n; + for (n = 0; n < sdeconv.shared.niter; n++) + if (sdeconv.iterate()) + break; + if (sdeconv.shared.niter && n >= sdeconv.shared.niter) + INFO("voxel [ " + str(dwi.index(0)) + " " + str(dwi.index(1)) + " " + str(dwi.index(2)) + + " ] did not reach full convergence"); - bool load_data (Image& dwi) { - if (mask.valid()) { - assign_pos_of (dwi, 0, 3).to (mask); - if (!mask.value()) - return false; - } + fod.row(3) = sdeconv.FOD(); + } - for (size_t n = 0; n < sdeconv.shared.dwis.size(); n++) { - dwi.index(3) = sdeconv.shared.dwis[n]; - data[n] = dwi.value(); - if (!std::isfinite (data[n])) - return false; - if (data[n] < 0.0) - data[n] = 0.0; - } +private: + DWI::SDeconv::CSD sdeconv; + Eigen::VectorXd data; + Image mask; - return true; + bool load_data(Image &dwi) { + if (mask.valid()) { + assign_pos_of(dwi, 0, 3).to(mask); + if (!mask.value()) + return false; } + for (size_t n = 0; n < sdeconv.shared.dwis.size(); n++) { + dwi.index(3) = sdeconv.shared.dwis[n]; + data[n] = dwi.value(); + if (!std::isfinite(data[n])) + return false; + if (data[n] < 0.0) + data[n] = 0.0; + } + return true; + } }; +class MSMT_Processor { +public: + MSMT_Processor(const DWI::SDeconv::MSMT_CSD::Shared &shared, + Image &mask_image, + vector> odf_images, + Image dwi_modelled = Image()) + : sdeconv(shared), + mask_image(mask_image), + odf_images(odf_images), + modelled_image(dwi_modelled), + dwi_data(shared.grad.rows()), + output_data(shared.problem.H.cols()) {} + + void operator()(Image &dwi_image) { + if (mask_image.valid()) { + assign_pos_of(dwi_image, 0, 3).to(mask_image); + if (!mask_image.value()) + return; + } + dwi_data = dwi_image.row(3); + sdeconv(dwi_data, output_data); + if (sdeconv.niter >= sdeconv.shared.problem.max_niter) { + INFO("voxel [ " + str(dwi_image.index(0)) + " " + str(dwi_image.index(1)) + " " + str(dwi_image.index(2)) + + " ] did not reach full convergence"); + } -class MSMT_Processor { - public: - MSMT_Processor (const DWI::SDeconv::MSMT_CSD::Shared& shared, Image& mask_image, - vector< Image > odf_images, Image dwi_modelled = Image()) : - sdeconv (shared), - mask_image (mask_image), - odf_images (odf_images), - modelled_image (dwi_modelled), - dwi_data (shared.grad.rows()), - output_data (shared.problem.H.cols()) { } - - - void operator() (Image& dwi_image) - { - if (mask_image.valid()) { - assign_pos_of (dwi_image, 0, 3).to (mask_image); - if (!mask_image.value()) - return; - } - - dwi_data = dwi_image.row(3); - - sdeconv (dwi_data, output_data); - if (sdeconv.niter >= sdeconv.shared.problem.max_niter) { - INFO ("voxel [ " + str (dwi_image.index(0)) + " " + str (dwi_image.index(1)) + " " + str (dwi_image.index(2)) + - " ] did not reach full convergence"); - } - - size_t j = 0; - for (size_t i = 0; i < odf_images.size(); ++i) { - assign_pos_of (dwi_image, 0, 3).to (odf_images[i]); - for (auto l = Loop(3)(odf_images[i]); l; ++l) - odf_images[i].value() = output_data[j++]; - } - - if (modelled_image.valid()) { - assign_pos_of (dwi_image, 0, 3).to (modelled_image); - dwi_data = sdeconv.shared.problem.H * output_data; - modelled_image.row(3) = dwi_data; - } + size_t j = 0; + for (size_t i = 0; i < odf_images.size(); ++i) { + assign_pos_of(dwi_image, 0, 3).to(odf_images[i]); + for (auto l = Loop(3)(odf_images[i]); l; ++l) + odf_images[i].value() = output_data[j++]; } + if (modelled_image.valid()) { + assign_pos_of(dwi_image, 0, 3).to(modelled_image); + dwi_data = sdeconv.shared.problem.H * output_data; + modelled_image.row(3) = dwi_data; + } + } - private: - DWI::SDeconv::MSMT_CSD sdeconv; - Image mask_image; - vector< Image > odf_images; - Image modelled_image; - Eigen::VectorXd dwi_data; - Eigen::VectorXd output_data; +private: + DWI::SDeconv::MSMT_CSD sdeconv; + Image mask_image; + vector> odf_images; + Image modelled_image; + Eigen::VectorXd dwi_data; + Eigen::VectorXd output_data; }; +void run() { - - - - - -void run () -{ - - auto header_in = Header::open (argument[1]); - Header header_out (header_in); + auto header_in = Header::open(argument[1]); + Header header_out(header_in); header_out.ndim() = 4; header_out.datatype() = DataType::Float32; header_out.datatype().set_byte_order_native(); - Stride::set_from_command_line (header_out, Stride::contiguous_along_axis (3, header_in)); + Stride::set_from_command_line(header_out, Stride::contiguous_along_axis(3, header_in)); auto mask = Image(); - auto opt = get_options ("mask"); + auto opt = get_options("mask"); if (opt.size()) { - mask = Header::open (opt[0][0]).get_image(); - check_dimensions (header_in, mask, 0, 3); + mask = Header::open(opt[0][0]).get_image(); + check_dimensions(header_in, mask, 0, 3); } int algorithm = argument[0]; if (algorithm == 0) { if (argument.size() != 4) - throw Exception ("CSD algorithm expects a single input response function and single output FOD image"); + throw Exception("CSD algorithm expects a single input response function and single output FOD image"); - DWI::SDeconv::CSD::Shared shared (header_in); + DWI::SDeconv::CSD::Shared shared(header_in); shared.parse_cmdline_options(); try { - shared.set_response (argument[2]); - } catch (Exception& e) { - throw Exception (e, "CSD algorithm expects second argument to be the input response function file"); + shared.set_response(argument[2]); + } catch (Exception &e) { + throw Exception(e, "CSD algorithm expects second argument to be the input response function file"); } shared.init(); - DWI::stash_DW_scheme (header_out, shared.grad); - PhaseEncoding::clear_scheme (header_out); + DWI::stash_DW_scheme(header_out, shared.grad); + PhaseEncoding::clear_scheme(header_out); header_out.size(3) = shared.nSH(); - auto fod = Image::create (argument[3], header_out); + auto fod = Image::create(argument[3], header_out); - CSD_Processor processor (shared, mask); - auto dwi = header_in.get_image().with_direct_io (3); - ThreadedLoop ("performing constrained spherical deconvolution", dwi, 0, 3) - .run (processor, dwi, fod); + CSD_Processor processor(shared, mask); + auto dwi = header_in.get_image().with_direct_io(3); + ThreadedLoop("performing constrained spherical deconvolution", dwi, 0, 3).run(processor, dwi, fod); } else if (algorithm == 1) { if (argument.size() % 2) - throw Exception ("MSMT_CSD algorithm expects pairs of (input response function & output FOD image) to be provided"); + throw Exception( + "MSMT_CSD algorithm expects pairs of (input response function & output FOD image) to be provided"); - DWI::SDeconv::MSMT_CSD::Shared shared (header_in); + DWI::SDeconv::MSMT_CSD::Shared shared(header_in); shared.parse_cmdline_options(); - const size_t num_tissues = (argument.size()-2)/2; + const size_t num_tissues = (argument.size() - 2) / 2; vector response_paths; vector odf_paths; for (size_t i = 0; i < num_tissues; ++i) { - response_paths.push_back (argument[i*2+2]); - odf_paths.push_back (argument[i*2+3]); + response_paths.push_back(argument[i * 2 + 2]); + odf_paths.push_back(argument[i * 2 + 3]); } try { - shared.set_responses (response_paths); - } catch (Exception& e) { - throw Exception (e, "MSMT_CSD algorithm expects the first file in each argument pair to be an input response function file"); + shared.set_responses(response_paths); + } catch (Exception &e) { + throw Exception( + e, "MSMT_CSD algorithm expects the first file in each argument pair to be an input response function file"); } shared.init(); - DWI::stash_DW_scheme (header_out, shared.grad); + DWI::stash_DW_scheme(header_out, shared.grad); - vector< Image > odfs; + vector> odfs; for (size_t i = 0; i < num_tissues; ++i) { - header_out.size (3) = Math::SH::NforL (shared.lmax[i]); - odfs.push_back (Image (Image::create (odf_paths[i], header_out))); + header_out.size(3) = Math::SH::NforL(shared.lmax[i]); + odfs.push_back(Image(Image::create(odf_paths[i], header_out))); } Image dwi_modelled; - auto opt = get_options ("predicted_signal"); + auto opt = get_options("predicted_signal"); if (opt.size()) - dwi_modelled = Image::create (opt[0][0], header_in); + dwi_modelled = Image::create(opt[0][0], header_in); - MSMT_Processor processor (shared, mask, odfs, dwi_modelled); - auto dwi = header_in.get_image().with_direct_io (3); - ThreadedLoop ("performing MSMT CSD (" - + str(shared.num_shells()) + " shell" + (shared.num_shells() > 1 ? "s" : "") + ", " - + str(num_tissues) + " tissue" + (num_tissues > 1 ? "s" : "") + ")", - dwi, 0, 3) - .run (processor, dwi); + MSMT_Processor processor(shared, mask, odfs, dwi_modelled); + auto dwi = header_in.get_image().with_direct_io(3); + ThreadedLoop("performing MSMT CSD (" + str(shared.num_shells()) + " shell" + (shared.num_shells() > 1 ? "s" : "") + + ", " + str(num_tissues) + " tissue" + (num_tissues > 1 ? "s" : "") + ")", + dwi, + 0, + 3) + .run(processor, dwi); } else { - assert (0); + assert(0); } - } - diff --git a/cmd/dwi2tensor.cpp b/cmd/dwi2tensor.cpp index a909f7868c..9cbedfd74c 100644 --- a/cmd/dwi2tensor.cpp +++ b/cmd/dwi2tensor.cpp @@ -14,16 +14,16 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "phase_encoding.h" -#include "progressbar.h" -#include "image.h" #include "algo/threaded_copy.h" +#include "command.h" +#include "dwi/directions/predefined.h" #include "dwi/gradient.h" #include "dwi/tensor.h" -#include "dwi/directions/predefined.h" -#include "math/constrained_least_squares.h" #include "file/matrix.h" +#include "image.h" +#include "math/constrained_least_squares.h" +#include "phase_encoding.h" +#include "progressbar.h" using namespace MR; using namespace App; @@ -32,291 +32,298 @@ using value_type = float; #define DEFAULT_NITER 2 -const char* const encoding_description[] = { - "The tensor coefficients are stored in the output image as follows:\n" - "volumes 0-5: D11, D22, D33, D12, D13, D23", - "If diffusion kurtosis is estimated using the -dkt option, these are stored as follows:\n" - "volumes 0-2: W1111, W2222, W3333\n" - "volumes 3-8: W1112, W1113, W1222, W1333, W2223, W2333\n" - "volumes 9-11: W1122, W1133, W2233\n" - "volumes 12-14: W1123, W1223, W1233", - nullptr -}; - - -void usage () -{ +const char *const encoding_description[] = { + "The tensor coefficients are stored in the output image as follows:\n" + "volumes 0-5: D11, D22, D33, D12, D13, D23", + "If diffusion kurtosis is estimated using the -dkt option, these are stored as follows:\n" + "volumes 0-2: W1111, W2222, W3333\n" + "volumes 3-8: W1112, W1113, W1222, W1333, W2223, W2333\n" + "volumes 9-11: W1122, W1133, W2233\n" + "volumes 12-14: W1123, W1223, W1233", + nullptr}; + +void usage() { AUTHOR = "Ben Jeurissen (ben.jeurissen@uantwerpen.be)"; SYNOPSIS = "Diffusion (kurtosis) tensor estimation"; DESCRIPTION - + "By default, the diffusion tensor (and optionally the kurtosis tensor) is fitted to " - "the log-signal in two steps: firstly, using weighted least-squares (WLS) with " - "weights based on the empirical signal intensities; secondly, by further iterated weighted " - "least-squares (IWLS) with weights determined by the signal predictions from the " - "previous iteration (by default, 2 iterations will be performed). This behaviour can " - "be altered in two ways:" + +"By default, the diffusion tensor (and optionally the kurtosis tensor) is fitted to " + "the log-signal in two steps: firstly, using weighted least-squares (WLS) with " + "weights based on the empirical signal intensities; secondly, by further iterated weighted " + "least-squares (IWLS) with weights determined by the signal predictions from the " + "previous iteration (by default, 2 iterations will be performed). This behaviour can " + "be altered in two ways:" - + "* The -ols option will cause the first fitting step to be performed using ordinary " - "least-squares (OLS); that is, all measurements contribute equally to the fit, instead of " - "the default behaviour of weighting based on the empirical signal intensities." + + "* The -ols option will cause the first fitting step to be performed using ordinary " + "least-squares (OLS); that is, all measurements contribute equally to the fit, instead of " + "the default behaviour of weighting based on the empirical signal intensities." - + "* The -iter option controls the number of iterations of the IWLS prodedure. If this is " - "set to zero, then the output model parameters will be those resulting from the first " - "fitting step only: either WLS by default, or OLS if the -ols option is used in conjunction " - "with -iter 0." + + "* The -iter option controls the number of iterations of the IWLS prodedure. If this is " + "set to zero, then the output model parameters will be those resulting from the first " + "fitting step only: either WLS by default, or OLS if the -ols option is used in conjunction " + "with -iter 0." - + "By default, the diffusion tensor (and optionally the kurtosis tensor) is fitted using " - "unconstrained optimization. This can result in unexpected diffusion parameters, " - "e.g. parameters that represent negative apparent diffusivities or negative apparent kurtoses, " - "or parameters that correspond to non-monotonic decay of the predicted signal. " - "By supplying the -constrain option, constrained optimization is performed instead " - "and such physically implausible parameters can be avoided. Depending on the presence " - " of the -dkt option, the -constrain option will enforce the following constraints:" + + "By default, the diffusion tensor (and optionally the kurtosis tensor) is fitted using " + "unconstrained optimization. This can result in unexpected diffusion parameters, " + "e.g. parameters that represent negative apparent diffusivities or negative apparent kurtoses, " + "or parameters that correspond to non-monotonic decay of the predicted signal. " + "By supplying the -constrain option, constrained optimization is performed instead " + "and such physically implausible parameters can be avoided. Depending on the presence " + " of the -dkt option, the -constrain option will enforce the following constraints:" - + "* Non-negative apparent diffusivity (always)." + + "* Non-negative apparent diffusivity (always)." - + "* Non-negative apparent kurtosis (when the -dkt option is provided)." + + "* Non-negative apparent kurtosis (when the -dkt option is provided)." - + "* Monotonic signal decay in the b = [0 b_max] range (when the -dkt option is provided)." + + "* Monotonic signal decay in the b = [0 b_max] range (when the -dkt option is provided)." - + encoding_description; + + encoding_description; ARGUMENTS - + Argument ("dwi", "the input dwi image.").type_image_in () - + Argument ("dt", "the output dt image.").type_image_out (); + +Argument("dwi", "the input dwi image.").type_image_in() + Argument("dt", "the output dt image.").type_image_out(); OPTIONS - + Option ("ols", "perform initial fit using an ordinary least-squares (OLS) fit (see Description).") + +Option("ols", "perform initial fit using an ordinary least-squares (OLS) fit (see Description).") - + Option ("iter","number of iterative reweightings for IWLS algorithm (default: " - + str(DEFAULT_NITER) + ") (see Description).") - + Argument ("integer").type_integer (0, 10) + + Option("iter", + "number of iterative reweightings for IWLS algorithm (default: " + str(DEFAULT_NITER) + + ") (see Description).") + + Argument("integer").type_integer(0, 10) - + Option ("constrain", "constrain fit to non-negative diffusivity and kurtosis as well as monotonic signal decay (see Description).") + + + Option( + "constrain", + "constrain fit to non-negative diffusivity and kurtosis as well as monotonic signal decay (see Description).") - + Option ("directions", - "specify the directions along which to apply the constraints " - "(by default, the built-in 300 direction set is used). These should be " - "supplied as a text file containing [ az el ] pairs for the directions.") - + Argument ("file").type_file_in() + + Option("directions", + "specify the directions along which to apply the constraints " + "(by default, the built-in 300 direction set is used). These should be " + "supplied as a text file containing [ az el ] pairs for the directions.") + + Argument("file").type_file_in() - + Option ("mask", "only perform computation within the specified binary brain mask image.") - + Argument ("image").type_image_in() + + Option("mask", "only perform computation within the specified binary brain mask image.") + + Argument("image").type_image_in() - + Option ("b0", "the output b0 image.") - + Argument ("image").type_image_out() + + Option("b0", "the output b0 image.") + Argument("image").type_image_out() - + Option ("dkt", "the output dkt image.") - + Argument ("image").type_image_out() + + Option("dkt", "the output dkt image.") + Argument("image").type_image_out() - + Option ("predicted_signal", "the predicted dwi image.") - + Argument ("image").type_image_out() + + Option("predicted_signal", "the predicted dwi image.") + Argument("image").type_image_out() - + DWI::GradImportOptions(); + + DWI::GradImportOptions(); REFERENCES - + "References based on fitting algorithm used:" - - + "* OLS, WLS:\n" - "Basser, P.J.; Mattiello, J.; LeBihan, D. " - "Estimation of the effective self-diffusion tensor from the NMR spin echo. " - "J Magn Reson B., 1994, 103, 247–254." - - + "* IWLS:\n" - "Veraart, J.; Sijbers, J.; Sunaert, S.; Leemans, A. & Jeurissen, B. " // Internal - "Weighted linear least squares estimation of diffusion MRI parameters: strengths, limitations, and pitfalls. " - "NeuroImage, 2013, 81, 335-346" - - + "* any of above with constraints:\n" - "Morez, J.; Szczepankiewicz, F; den Dekker, A. J.; Vanhevel, F.; Sijbers, J. & Jeurissen, B. " // Internal - "Optimal experimental design and estimation for q-space trajectory imaging. " - "Human Brain Mapping, In press"; + +"References based on fitting algorithm used:" + + + "* OLS, WLS:\n" + "Basser, P.J.; Mattiello, J.; LeBihan, D. " + "Estimation of the effective self-diffusion tensor from the NMR spin echo. " + "J Magn Reson B., 1994, 103, 247–254." + + + "* IWLS:\n" + "Veraart, J.; Sijbers, J.; Sunaert, S.; Leemans, A. & Jeurissen, B. " // Internal + "Weighted linear least squares estimation of diffusion MRI parameters: strengths, limitations, and pitfalls. " + "NeuroImage, 2013, 81, 335-346" + + + "* any of above with constraints:\n" + "Morez, J.; Szczepankiewicz, F; den Dekker, A. J.; Vanhevel, F.; Sijbers, J. & Jeurissen, B. " // Internal + "Optimal experimental design and estimation for q-space trajectory imaging. " + "Human Brain Mapping, In press"; } +template class Processor { +public: + Processor(const Eigen::MatrixXd &A, + const Eigen::MatrixXd &Aneq, + const bool ols, + const int iter, + const MASKType &mask_image, + const B0Type &b0_image, + const DTType &dt_image, + const DKTType &dkt_image, + const PredictType &predict_image) + : mask_image(mask_image), + b0_image(b0_image), + dt_image(dt_image), + dkt_image(dkt_image), + predict_image(predict_image), + dwi(A.rows()), + x(A.cols()), + w(Eigen::VectorXd::Ones(A.rows())), + work(A.cols(), A.cols()), + llt(work.rows()), + A(A), + Aneq(Aneq), + ols(ols), + maxit(iter) {} + + template void operator()(DWIType &dwi_image) { + if (mask_image.valid()) { + assign_pos_of(dwi_image, 0, 3).to(mask_image); + if (!mask_image.value()) + return; + } + + for (auto l = Loop(3)(dwi_image); l; ++l) + dwi[dwi_image.index(3)] = dwi_image.value(); + notnan = dwi.array().isFinite().template cast(); -template -class Processor { - public: - Processor (const Eigen::MatrixXd& A, const Eigen::MatrixXd& Aneq, const bool ols, const int iter, - const MASKType& mask_image, const B0Type& b0_image, const DTType& dt_image, const DKTType& dkt_image, const PredictType& predict_image) : - mask_image (mask_image), - b0_image (b0_image), - dt_image (dt_image), - dkt_image (dkt_image), - predict_image (predict_image), - dwi(A.rows()), - x(A.cols()), - w(Eigen::VectorXd::Ones (A.rows())), - work(A.cols(),A.cols()), - llt(work.rows()), - A(A), - Aneq(Aneq), - ols (ols), - maxit(iter) { } - - template - void operator() (DWIType& dwi_image) - { - if (mask_image.valid()) { - assign_pos_of (dwi_image, 0, 3).to (mask_image); - if (!mask_image.value()) - return; - } - - for (auto l = Loop (3) (dwi_image); l; ++l) - dwi[dwi_image.index(3)] = dwi_image.value(); - - notnan = dwi.array().isFinite().template cast(); - - double small_intensity = 1.0e-6 * dwi.maxCoeff(); - for (int i = 0; i < dwi.rows(); i++) { - if (notnan[i] == 0 || dwi[i] < small_intensity) - dwi[i] = small_intensity; - w[i] = notnan[i] * ( ols ? 1.0 : dwi[i] ); - dwi[i] = std::log (dwi[i]); - } - - for (int it = 0; it <= maxit; it++) { - if (Aneq.rows() > 0) { - auto problem = Math::ICLS::Problem (w.asDiagonal()*A, Aneq); - Math::ICLS::Solver solver (problem); - solver (x, w.asDiagonal()*dwi); - } else { - work.setZero(); - work.selfadjointView().rankUpdate (A.transpose()*w.asDiagonal()); - x = llt.compute (work.selfadjointView()).solve(A.transpose()*w.asDiagonal()*w.asDiagonal()*dwi); - } - if (maxit > 1) - w = (A*x).array().exp() * notnan.array(); - } - - if (b0_image.valid()) { - assign_pos_of (dwi_image, 0, 3).to (b0_image); - b0_image.value() = exp(x[0]); - } - - if (dt_image.valid()) { - assign_pos_of (dwi_image, 0, 3).to (dt_image); - for (auto l = Loop(3)(dt_image); l; ++l) { - dt_image.value() = x[dt_image.index(3)+1]; - } - } - - if (dkt_image.valid()) { - assign_pos_of (dwi_image, 0, 3).to (dkt_image); - double adc_sq = Math::pow2(x[1]+x[2]+x[3])/9.0 + 1e-18; - for (auto l = Loop(3)(dkt_image); l; ++l) { - dkt_image.value() = x[dkt_image.index(3)+7]/adc_sq; - } - } - - if (predict_image.valid()) { - assign_pos_of (dwi_image, 0, 3).to (predict_image); - dwi = (A*x).array().exp(); - for (auto l = Loop(3)(predict_image); l; ++l) { - predict_image.value() = dwi[predict_image.index(3)]; - } - } + double small_intensity = 1.0e-6 * dwi.maxCoeff(); + for (int i = 0; i < dwi.rows(); i++) { + if (notnan[i] == 0 || dwi[i] < small_intensity) + dwi[i] = small_intensity; + w[i] = notnan[i] * (ols ? 1.0 : dwi[i]); + dwi[i] = std::log(dwi[i]); + } + + for (int it = 0; it <= maxit; it++) { + if (Aneq.rows() > 0) { + auto problem = Math::ICLS::Problem(w.asDiagonal() * A, Aneq); + Math::ICLS::Solver solver(problem); + solver(x, w.asDiagonal() * dwi); + } else { + work.setZero(); + work.selfadjointView().rankUpdate(A.transpose() * w.asDiagonal()); + x = llt.compute(work.selfadjointView()) + .solve(A.transpose() * w.asDiagonal() * w.asDiagonal() * dwi); + } + if (maxit > 1) + w = (A * x).array().exp() * notnan.array(); + } + + if (b0_image.valid()) { + assign_pos_of(dwi_image, 0, 3).to(b0_image); + b0_image.value() = exp(x[0]); + } + if (dt_image.valid()) { + assign_pos_of(dwi_image, 0, 3).to(dt_image); + for (auto l = Loop(3)(dt_image); l; ++l) { + dt_image.value() = x[dt_image.index(3) + 1]; } + } + + if (dkt_image.valid()) { + assign_pos_of(dwi_image, 0, 3).to(dkt_image); + double adc_sq = Math::pow2(x[1] + x[2] + x[3]) / 9.0 + 1e-18; + for (auto l = Loop(3)(dkt_image); l; ++l) { + dkt_image.value() = x[dkt_image.index(3) + 7] / adc_sq; + } + } + + if (predict_image.valid()) { + assign_pos_of(dwi_image, 0, 3).to(predict_image); + dwi = (A * x).array().exp(); + for (auto l = Loop(3)(predict_image); l; ++l) { + predict_image.value() = dwi[predict_image.index(3)]; + } + } + } - private: - MASKType mask_image; - B0Type b0_image; - DTType dt_image; - DKTType dkt_image; - PredictType predict_image; - Eigen::VectorXd dwi; - Eigen::VectorXd x; - Eigen::VectorXd w; - Eigen::VectorXd notnan; - Eigen::MatrixXd work; - Eigen::LLT llt; - const Eigen::MatrixXd& A; - const Eigen::MatrixXd& Aneq; - const bool ols; - const int maxit; +private: + MASKType mask_image; + B0Type b0_image; + DTType dt_image; + DKTType dkt_image; + PredictType predict_image; + Eigen::VectorXd dwi; + Eigen::VectorXd x; + Eigen::VectorXd w; + Eigen::VectorXd notnan; + Eigen::MatrixXd work; + Eigen::LLT llt; + const Eigen::MatrixXd &A; + const Eigen::MatrixXd &Aneq; + const bool ols; + const int maxit; }; template -inline Processor processor (const Eigen::MatrixXd& A, const Eigen::MatrixXd& Aneq, const bool ols, const int iter, const MASKType& mask_image, const B0Type& b0_image, const DTType& dt_image, const DKTType& dkt_image, const PredictType& predict_image) { - return { A, Aneq, ols, iter, mask_image, b0_image, dt_image, dkt_image, predict_image }; +inline Processor processor(const Eigen::MatrixXd &A, + const Eigen::MatrixXd &Aneq, + const bool ols, + const int iter, + const MASKType &mask_image, + const B0Type &b0_image, + const DTType &dt_image, + const DKTType &dkt_image, + const PredictType &predict_image) { + return {A, Aneq, ols, iter, mask_image, b0_image, dt_image, dkt_image, predict_image}; } -void run () -{ - auto dwi = Header::open (argument[0]).get_image(); - auto grad = DWI::get_DW_scheme (dwi); +void run() { + auto dwi = Header::open(argument[0]).get_image(); + auto grad = DWI::get_DW_scheme(dwi); Image mask; - auto opt = get_options ("mask"); + auto opt = get_options("mask"); if (opt.size()) { - mask = Image::open (opt[0][0]); - check_dimensions (dwi, mask, 0, 3); + mask = Image::open(opt[0][0]); + check_dimensions(dwi, mask, 0, 3); } - bool ols = get_options ("ols").size(); + bool ols = get_options("ols").size(); // depending on whether first (initialisation) loop should be considered an iteration - auto iter = get_option_value ("iter", DEFAULT_NITER); + auto iter = get_option_value("iter", DEFAULT_NITER); - Header header (dwi); + Header header(dwi); header.datatype() = DataType::Float32; header.ndim() = 4; - DWI::stash_DW_scheme (header, grad); - PhaseEncoding::clear_scheme (header); + DWI::stash_DW_scheme(header, grad); + PhaseEncoding::clear_scheme(header); Image predict; - opt = get_options ("predicted_signal"); + opt = get_options("predicted_signal"); if (opt.size()) - predict = Image::create (opt[0][0], header); + predict = Image::create(opt[0][0], header); header.size(3) = 6; - auto dt = Image::create (argument[1], header); + auto dt = Image::create(argument[1], header); Image b0; - opt = get_options ("b0"); + opt = get_options("b0"); if (opt.size()) { header.ndim() = 3; - b0 = Image::create (opt[0][0], header); + b0 = Image::create(opt[0][0], header); } Image dkt; - opt = get_options ("dkt"); - bool dki = opt.size()>0; + opt = get_options("dkt"); + bool dki = opt.size() > 0; if (dki) { header.ndim() = 4; header.size(3) = 15; - dkt = Image::create (opt[0][0], header); + dkt = Image::create(opt[0][0], header); } - Eigen::MatrixXd A = -DWI::grad2bmatrix (grad, dki); + Eigen::MatrixXd A = -DWI::grad2bmatrix(grad, dki); - bool constrain = get_options ("constrain").size(); + bool constrain = get_options("constrain").size(); Eigen::MatrixXd Aneq; if (constrain) { - opt = get_options ("directions"); - const Eigen::MatrixXd constr_dirs = opt.size() ? - File::Matrix::load_matrix (opt[0][0]) : - Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()); - Eigen::MatrixXd tmp = DWI::grad2bmatrix (constr_dirs, dki); + opt = get_options("directions"); + const Eigen::MatrixXd constr_dirs = + opt.size() ? File::Matrix::load_matrix(opt[0][0]) + : Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()); + Eigen::MatrixXd tmp = DWI::grad2bmatrix(constr_dirs, dki); if (dki) { auto maxb = grad.col(3).maxCoeff(); - Aneq = Eigen::MatrixXd::Zero(tmp.rows()*2,tmp.cols()); - //Aneq.block(tmp.rows()*0,1,tmp.rows(), 6) = tmp.block(0,1,tmp.rows(), 6); --> redundant constraint - Aneq.block(tmp.rows()*0,7,tmp.rows(),15) = -6.0*tmp.block(0,7,tmp.rows(),15); - Aneq.block(tmp.rows()*1,1,tmp.rows(), 6) = tmp.block(0,1,tmp.rows(), 6); - Aneq.block(tmp.rows()*1,7,tmp.rows(),15) = (maxb/3.0)*6.0*tmp.block(0,7,tmp.rows(),15); + Aneq = Eigen::MatrixXd::Zero(tmp.rows() * 2, tmp.cols()); + // Aneq.block(tmp.rows()*0,1,tmp.rows(), 6) = tmp.block(0,1,tmp.rows(), 6); --> redundant + // constraint + Aneq.block(tmp.rows() * 0, 7, tmp.rows(), 15) = -6.0 * tmp.block(0, 7, tmp.rows(), 15); + Aneq.block(tmp.rows() * 1, 1, tmp.rows(), 6) = tmp.block(0, 1, tmp.rows(), 6); + Aneq.block(tmp.rows() * 1, 7, tmp.rows(), 15) = (maxb / 3.0) * 6.0 * tmp.block(0, 7, tmp.rows(), 15); } else { - Aneq = Eigen::MatrixXd::Zero(tmp.rows()*1,tmp.cols()); - Aneq.block(tmp.rows()*0,1,tmp.rows(), 6) = tmp.block(0,1,tmp.rows(), 6); + Aneq = Eigen::MatrixXd::Zero(tmp.rows() * 1, tmp.cols()); + Aneq.block(tmp.rows() * 0, 1, tmp.rows(), 6) = tmp.block(0, 1, tmp.rows(), 6); } } - ThreadedLoop("computing tensors", dwi, 0, 3).run (processor (A, Aneq, ols, iter, mask, b0, dt, dkt, predict), dwi); + ThreadedLoop("computing tensors", dwi, 0, 3).run(processor(A, Aneq, ols, iter, mask, b0, dt, dkt, predict), dwi); } diff --git a/cmd/dwidenoise.cpp b/cmd/dwidenoise.cpp index b0278a949d..a3ef3d615e 100644 --- a/cmd/dwidenoise.cpp +++ b/cmd/dwidenoise.cpp @@ -14,162 +14,163 @@ * For more details, see http://www.mrtrix.org/. */ - #include "command.h" #include "image.h" #include #include - using namespace MR; using namespace App; -const char* const dtypes[] = { "float32", "float64", NULL }; +const char *const dtypes[] = {"float32", "float64", NULL}; -const char* const estimators[] = { "exp1", "exp2", NULL }; +const char *const estimators[] = {"exp1", "exp2", NULL}; - -void usage () -{ +void usage() { SYNOPSIS = "dMRI noise level estimation and denoising using Marchenko-Pastur PCA"; DESCRIPTION - + "DWI data denoising and noise map estimation by exploiting data redundancy in the PCA domain " - "using the prior knowledge that the eigenspectrum of random covariance matrices is described by " - "the universal Marchenko-Pastur (MP) distribution. Fitting the MP distribution to the spectrum " - "of patch-wise signal matrices hence provides an estimator of the noise level 'sigma', as was " - "first shown in Veraart et al. (2016) and later improved in Cordero-Grande et al. (2019). This " - "noise level estimate then determines the optimal cut-off for PCA denoising." + +"DWI data denoising and noise map estimation by exploiting data redundancy in the PCA domain " + "using the prior knowledge that the eigenspectrum of random covariance matrices is described by " + "the universal Marchenko-Pastur (MP) distribution. Fitting the MP distribution to the spectrum " + "of patch-wise signal matrices hence provides an estimator of the noise level 'sigma', as was " + "first shown in Veraart et al. (2016) and later improved in Cordero-Grande et al. (2019). This " + "noise level estimate then determines the optimal cut-off for PCA denoising." - + "Important note: image denoising must be performed as the first step of the image processing pipeline. " - "The routine will fail if interpolation or smoothing has been applied to the data prior to denoising." + + "Important note: image denoising must be performed as the first step of the image processing pipeline. " + "The routine will fail if interpolation or smoothing has been applied to the data prior to denoising." - + "Note that this function does not correct for non-Gaussian noise biases present in " - "magnitude-reconstructed MRI images. If available, including the MRI phase data can " - "reduce such non-Gaussian biases, and the command now supports complex input data."; + + "Note that this function does not correct for non-Gaussian noise biases present in " + "magnitude-reconstructed MRI images. If available, including the MRI phase data can " + "reduce such non-Gaussian biases, and the command now supports complex input data."; AUTHOR = "Daan Christiaens (daan.christiaens@kcl.ac.uk) & " "Jelle Veraart (jelle.veraart@nyumc.org) & " "J-Donald Tournier (jdtournier@gmail.com)"; REFERENCES - + "Veraart, J.; Novikov, D.S.; Christiaens, D.; Ades-aron, B.; Sijbers, J. & Fieremans, E. " // Internal - "Denoising of diffusion MRI using random matrix theory. " - "NeuroImage, 2016, 142, 394-406, doi: 10.1016/j.neuroimage.2016.08.016" + +"Veraart, J.; Novikov, D.S.; Christiaens, D.; Ades-aron, B.; Sijbers, J. & Fieremans, E. " // Internal + "Denoising of diffusion MRI using random matrix theory. " + "NeuroImage, 2016, 142, 394-406, doi: 10.1016/j.neuroimage.2016.08.016" - + "Veraart, J.; Fieremans, E. & Novikov, D.S. " // Internal - "Diffusion MRI noise mapping using random matrix theory. " - "Magn. Res. Med., 2016, 76(5), 1582-1593, doi: 10.1002/mrm.26059" + + "Veraart, J.; Fieremans, E. & Novikov, D.S. " // Internal + "Diffusion MRI noise mapping using random matrix theory. " + "Magn. Res. Med., 2016, 76(5), 1582-1593, doi: 10.1002/mrm.26059" - + "Cordero-Grande, L.; Christiaens, D.; Hutter, J.; Price, A.N.; Hajnal, J.V. " // Internal - "Complex diffusion-weighted image estimation via matrix recovery under general noise models. " - "NeuroImage, 2019, 200, 391-404, doi: 10.1016/j.neuroimage.2019.06.039"; + + "Cordero-Grande, L.; Christiaens, D.; Hutter, J.; Price, A.N.; Hajnal, J.V. " // Internal + "Complex diffusion-weighted image estimation via matrix recovery under general noise models. " + "NeuroImage, 2019, 200, 391-404, doi: 10.1016/j.neuroimage.2019.06.039"; ARGUMENTS - + Argument ("dwi", "the input diffusion-weighted image.").type_image_in () - - + Argument ("out", "the output denoised DWI image.").type_image_out (); + +Argument("dwi", "the input diffusion-weighted image.").type_image_in() + + Argument("out", "the output denoised DWI image.").type_image_out(); OPTIONS - + Option ("mask", "Only process voxels within the specified binary brain mask image.") - + Argument ("image").type_image_in() - - + Option ("extent", "Set the patch size of the denoising filter. " - "By default, the command will select the smallest isotropic patch size " - "that exceeds the number of DW images in the input data, e.g., 5x5x5 for " - "data with <= 125 DWI volumes, 7x7x7 for data with <= 343 DWI volumes, etc.") - + Argument ("window").type_sequence_int () - - + Option ("noise", "The output noise map, i.e., the estimated noise level 'sigma' in the data. " - "Note that on complex input data, this will be the total noise level across " - "real and imaginary channels, so a scale factor sqrt(2) applies.") - + Argument ("level").type_image_out() - - + Option ("rank", "The selected signal rank of the output denoised image.") - + Argument ("cutoff").type_image_out() - - + Option ("datatype", "Datatype for the eigenvalue decomposition (single or double precision). " - "For complex input data, this will select complex float32 or complex float64 datatypes.") - + Argument ("float32/float64").type_choice(dtypes) - - + Option ("estimator", "Select the noise level estimator (default = Exp2), either: \n" - "* Exp1: the original estimator used in Veraart et al. (2016), or \n" - "* Exp2: the improved estimator introduced in Cordero-Grande et al. (2019).") - + Argument ("Exp1/Exp2").type_choice(estimators); - - - COPYRIGHT = "Copyright (c) 2016 New York University, University of Antwerp, and the MRtrix3 contributors \n \n" - "Permission is hereby granted, free of charge, to any non-commercial entity ('Recipient') obtaining a copy of this software and " - "associated documentation files (the 'Software'), to the Software solely for non-commercial research, including the rights to " + +Option("mask", "Only process voxels within the specified binary brain mask image.") + + Argument("image").type_image_in() + + + Option("extent", + "Set the patch size of the denoising filter. " + "By default, the command will select the smallest isotropic patch size " + "that exceeds the number of DW images in the input data, e.g., 5x5x5 for " + "data with <= 125 DWI volumes, 7x7x7 for data with <= 343 DWI volumes, etc.") + + Argument("window").type_sequence_int() + + + Option("noise", + "The output noise map, i.e., the estimated noise level 'sigma' in the data. " + "Note that on complex input data, this will be the total noise level across " + "real and imaginary channels, so a scale factor sqrt(2) applies.") + + Argument("level").type_image_out() + + + Option("rank", "The selected signal rank of the output denoised image.") + Argument("cutoff").type_image_out() + + + Option("datatype", + "Datatype for the eigenvalue decomposition (single or double precision). " + "For complex input data, this will select complex float32 or complex float64 datatypes.") + + Argument("float32/float64").type_choice(dtypes) + + + Option("estimator", + "Select the noise level estimator (default = Exp2), either: \n" + "* Exp1: the original estimator used in Veraart et al. (2016), or \n" + "* Exp2: the improved estimator introduced in Cordero-Grande et al. (2019).") + + Argument("Exp1/Exp2").type_choice(estimators); + + COPYRIGHT = + "Copyright (c) 2016 New York University, University of Antwerp, and the MRtrix3 contributors \n \n" + "Permission is hereby granted, free of charge, to any non-commercial entity ('Recipient') obtaining a copy of " + "this software and " + "associated documentation files (the 'Software'), to the Software solely for non-commercial research, including " + "the rights to " "use, copy and modify the Software, subject to the following conditions: \n \n" - "\t 1. The above copyright notice and this permission notice shall be included by Recipient in all copies or substantial portions of " + "\t 1. The above copyright notice and this permission notice shall be included by Recipient in all copies or " + "substantial portions of " "the Software. \n \n" - "\t 2. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES" - "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE" - "LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR" + "\t 2. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT " + "LIMITED TO THE WARRANTIES" + "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR " + "COPYRIGHT HOLDERS BE" + "LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING " + "FROM, OUT OF OR" "IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \n \n" - "\t 3. In no event shall NYU be liable for direct, indirect, special, incidental or consequential damages in connection with the Software. " - "Recipient will defend, indemnify and hold NYU harmless from any claims or liability resulting from the use of the Software by recipient. \n \n" - "\t 4. Neither anything contained herein nor the delivery of the Software to recipient shall be deemed to grant the Recipient any right or " + "\t 3. In no event shall NYU be liable for direct, indirect, special, incidental or consequential damages in " + "connection with the Software. " + "Recipient will defend, indemnify and hold NYU harmless from any claims or liability resulting from the use of " + "the Software by recipient. \n \n" + "\t 4. Neither anything contained herein nor the delivery of the Software to recipient shall be deemed to grant " + "the Recipient any right or " "licenses under any patents or patent application owned by NYU. \n \n" "\t 5. The Software may only be used for non-commercial research and may not be used for clinical care. \n \n" "\t 6. Any publication by Recipient of research involving the Software shall cite the references listed below."; - } - using real_type = float; - -template -class DenoisingFunctor { - +template class DenoisingFunctor { public: - using MatrixType = Eigen::Matrix; using SValsType = Eigen::VectorXd; - DenoisingFunctor (int ndwi, const vector& extent, - Image& mask, Image& noise, - Image& rank, bool exp1) - : extent {{extent[0]/2, extent[1]/2, extent[2]/2}}, - m (ndwi), - n (extent[0]*extent[1]*extent[2]), - r (std::min(m,n)), - q (std::max(m,n)), - exp1(exp1), - X (m,n), - XtX (r, r), - eig (r), - s (r), - pos {{0, 0, 0}}, - mask (mask), - noise (noise), - rankmap (rank) - { } - - template - void operator () (ImageType& dwi, ImageType& out) - { + DenoisingFunctor(int ndwi, + const vector &extent, + Image &mask, + Image &noise, + Image &rank, + bool exp1) + : extent{{extent[0] / 2, extent[1] / 2, extent[2] / 2}}, + m(ndwi), + n(extent[0] * extent[1] * extent[2]), + r(std::min(m, n)), + q(std::max(m, n)), + exp1(exp1), + X(m, n), + XtX(r, r), + eig(r), + s(r), + pos{{0, 0, 0}}, + mask(mask), + noise(noise), + rankmap(rank) {} + + template void operator()(ImageType &dwi, ImageType &out) { // Process voxels in mask only if (mask.valid()) { - assign_pos_of (dwi, 0, 3).to (mask); + assign_pos_of(dwi, 0, 3).to(mask); if (!mask.value()) return; } // Load data in local window - load_data (dwi); + load_data(dwi); // Compute Eigendecomposition: if (m <= n) XtX.template triangularView() = X * X.adjoint(); else XtX.template triangularView() = X.adjoint() * X; - eig.compute (XtX); + eig.compute(XtX); // eigenvalues sorted in increasing order: s = eig.eigenvalues().template cast(); @@ -178,45 +179,44 @@ class DenoisingFunctor { double clam = 0.0; sigma2 = 0.0; ssize_t cutoff_p = 0; - for (ssize_t p = 0; p < r; ++p) // p+1 is the number of noise components - { // (as opposed to the paper where p is defined as the number of signal components) + for (ssize_t p = 0; p < r; ++p) // p+1 is the number of noise components + { // (as opposed to the paper where p is defined as the number of signal components) double lam = std::max(s[p], 0.0) / q; clam += lam; - double gam = double(p+1) / (exp1 ? q : q-(r-p-1)); - double sigsq1 = clam / double(p+1); + double gam = double(p + 1) / (exp1 ? q : q - (r - p - 1)); + double sigsq1 = clam / double(p + 1); double sigsq2 = (lam - lam_r) / (4.0 * std::sqrt(gam)); // sigsq2 > sigsq1 if signal else noise if (sigsq2 < sigsq1) { sigma2 = sigsq1; - cutoff_p = p+1; + cutoff_p = p + 1; } } if (cutoff_p > 0) { // recombine data using only eigenvectors above threshold: - s.head (cutoff_p).setZero(); - s.tail (r-cutoff_p).setOnes(); + s.head(cutoff_p).setZero(); + s.tail(r - cutoff_p).setOnes(); if (m <= n) - X.col (n/2) = eig.eigenvectors() * ( s.cast().asDiagonal() * ( eig.eigenvectors().adjoint() * X.col(n/2) )); + X.col(n / 2) = eig.eigenvectors() * (s.cast().asDiagonal() * (eig.eigenvectors().adjoint() * X.col(n / 2))); else - X.col (n/2) = X * ( eig.eigenvectors() * ( s.cast().asDiagonal() * eig.eigenvectors().adjoint().col(n/2) )); + X.col(n / 2) = X * (eig.eigenvectors() * (s.cast().asDiagonal() * eig.eigenvectors().adjoint().col(n / 2))); } // Store output assign_pos_of(dwi).to(out); - out.row(3) = X.col(n/2); + out.row(3) = X.col(n / 2); // store noise map if requested: if (noise.valid()) { assign_pos_of(dwi, 0, 3).to(noise); - noise.value() = real_type (std::sqrt(sigma2)); + noise.value() = real_type(std::sqrt(sigma2)); } // store rank map if requested: if (rankmap.valid()) { assign_pos_of(dwi, 0, 3).to(rankmap); - rankmap.value() = uint16_t (r - cutoff_p); + rankmap.value() = uint16_t(r - cutoff_p); } - } private: @@ -233,9 +233,10 @@ class DenoisingFunctor { Image noise; Image rankmap; - template - void load_data (ImageType& dwi) { - pos[0] = dwi.index(0); pos[1] = dwi.index(1); pos[2] = dwi.index(2); + template void load_data(ImageType &dwi) { + pos[0] = dwi.index(0); + pos[1] = dwi.index(1); + pos[2] = dwi.index(2); // fill patch X.setZero(); size_t k = 0; @@ -258,71 +259,71 @@ class DenoisingFunctor { inline size_t wrapindex(int r, int axis, int max) const { // patch handling at image edges int rr = pos[axis] + r; - if (rr < 0) rr = extent[axis] - r; - if (rr >= max) rr = (max-1) - extent[axis] - r; + if (rr < 0) + rr = extent[axis] - r; + if (rr >= max) + rr = (max - 1) - extent[axis] - r; return rr; } - }; - template -void process_image (Header& data, Image& mask, Image& noise, Image& rank, - const std::string& output_name, const vector& extent, bool exp1) - { - auto input = data.get_image().with_direct_io(3); - // create output - Header header (data); - header.datatype() = DataType::from(); - auto output = Image::create (output_name, header); - // run - DenoisingFunctor func (data.size(3), extent, mask, noise, rank, exp1); - ThreadedLoop ("running MP-PCA denoising", data, 0, 3).run (func, input, output); - } - - +void process_image(Header &data, + Image &mask, + Image &noise, + Image &rank, + const std::string &output_name, + const vector &extent, + bool exp1) { + auto input = data.get_image().with_direct_io(3); + // create output + Header header(data); + header.datatype() = DataType::from(); + auto output = Image::create(output_name, header); + // run + DenoisingFunctor func(data.size(3), extent, mask, noise, rank, exp1); + ThreadedLoop("running MP-PCA denoising", data, 0, 3).run(func, input, output); +} -void run () -{ - auto dwi = Header::open (argument[0]); +void run() { + auto dwi = Header::open(argument[0]); if (dwi.ndim() != 4 || dwi.size(3) <= 1) - throw Exception ("input image must be 4-dimensional"); + throw Exception("input image must be 4-dimensional"); Image mask; - auto opt = get_options ("mask"); + auto opt = get_options("mask"); if (opt.size()) { - mask = Image::open (opt[0][0]); - check_dimensions (mask, dwi, 0, 3); + mask = Image::open(opt[0][0]); + check_dimensions(mask, dwi, 0, 3); } opt = get_options("extent"); vector extent; if (opt.size()) { - extent = parse_ints (opt[0][0]); + extent = parse_ints(opt[0][0]); if (extent.size() == 1) extent = {extent[0], extent[0], extent[0]}; if (extent.size() != 3) - throw Exception ("-extent must be either a scalar or a list of length 3"); + throw Exception("-extent must be either a scalar or a list of length 3"); for (int i = 0; i < 3; i++) { if (!(extent[i] & 1)) - throw Exception ("-extent must be a (list of) odd numbers"); + throw Exception("-extent must be a (list of) odd numbers"); if (extent[i] > dwi.size(i)) - throw Exception ("-extent must not exceed the image dimensions"); + throw Exception("-extent must not exceed the image dimensions"); } } else { uint32_t e = 1; - while (e*e*e < dwi.size(3)) + while (e * e * e < dwi.size(3)) e += 2; - extent = { std::min(e, uint32_t(dwi.size(0))), - std::min(e, uint32_t(dwi.size(1))), - std::min(e, uint32_t(dwi.size(2))) }; + extent = { + std::min(e, uint32_t(dwi.size(0))), std::min(e, uint32_t(dwi.size(1))), std::min(e, uint32_t(dwi.size(2)))}; } INFO("selected patch size: " + str(extent[0]) + " x " + str(extent[1]) + " x " + str(extent[2]) + "."); - bool exp1 = get_option_value("estimator", 1) == 0; // default: Exp2 (unbiased estimator) + bool exp1 = get_option_value("estimator", 1) == 0; // default: Exp2 (unbiased estimator) - if (std::min(dwi.size(3), extent[0]*extent[1]*extent[2]) < 15) { + if (std::min(dwi.size(3), extent[0] * extent[1] * extent[2]) < 15) { WARN("The number of volumes or the patch size is small. This may lead to discretisation effects " "in the noise level and cause inconsistent denoising between adjacent voxels."); } @@ -330,45 +331,41 @@ void run () Image noise; opt = get_options("noise"); if (opt.size()) { - Header header (dwi); + Header header(dwi); header.ndim() = 3; header.datatype() = DataType::Float32; - noise = Image::create (opt[0][0], header); + noise = Image::create(opt[0][0], header); } Image rank; opt = get_options("rank"); if (opt.size()) { - Header header (dwi); + Header header(dwi); header.ndim() = 3; header.datatype() = DataType::UInt16; header.reset_intensity_scaling(); - rank = Image::create (opt[0][0], header); + rank = Image::create(opt[0][0], header); } - int prec = get_option_value("datatype", 0); // default: single precision - if (dwi.datatype().is_complex()) prec += 2; // support complex input data + int prec = get_option_value("datatype", 0); // default: single precision + if (dwi.datatype().is_complex()) + prec += 2; // support complex input data switch (prec) { - case 0: - INFO("select real float32 for processing"); - process_image(dwi, mask, noise, rank, argument[1], extent, exp1); - break; - case 1: - INFO("select real float64 for processing"); - process_image(dwi, mask, noise, rank, argument[1], extent, exp1); - break; - case 2: - INFO("select complex float32 for processing"); - process_image(dwi, mask, noise, rank, argument[1], extent, exp1); - break; - case 3: - INFO("select complex float64 for processing"); - process_image(dwi, mask, noise, rank, argument[1], extent, exp1); - break; + case 0: + INFO("select real float32 for processing"); + process_image(dwi, mask, noise, rank, argument[1], extent, exp1); + break; + case 1: + INFO("select real float64 for processing"); + process_image(dwi, mask, noise, rank, argument[1], extent, exp1); + break; + case 2: + INFO("select complex float32 for processing"); + process_image(dwi, mask, noise, rank, argument[1], extent, exp1); + break; + case 3: + INFO("select complex float64 for processing"); + process_image(dwi, mask, noise, rank, argument[1], extent, exp1); + break; } - - - } - - diff --git a/cmd/dwiextract.cpp b/cmd/dwiextract.cpp index 9c384ec24b..c87d398608 100644 --- a/cmd/dwiextract.cpp +++ b/cmd/dwiextract.cpp @@ -14,100 +14,93 @@ * For more details, see http://www.mrtrix.org/. */ +#include "adapter/extract.h" +#include "algo/loop.h" #include "command.h" +#include "dwi/gradient.h" #include "image.h" #include "phase_encoding.h" #include "progressbar.h" -#include "dwi/gradient.h" -#include "algo/loop.h" -#include "adapter/extract.h" - using namespace MR; using namespace App; using value_type = float; -void usage () -{ +void usage() { - AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com) and Robert E. Smith (robert.smith@florey.edu.au)"; + AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and Thijs Dhollander (thijs.dhollander@gmail.com) and Robert " + "E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Extract diffusion-weighted volumes, b=0 volumes, or certain shells from a DWI dataset"; EXAMPLES - + Example ("Calculate the mean b=0 image from a 4D DWI series", - "dwiextract dwi.mif - -bzero | mrmath - mean mean_bzero.mif -axis 3", - "The dwiextract command extracts all volumes for which the b-value is " - "(approximately) zero; the resulting 4D image can then be provided to " - "the mrmath command to calculate the mean intensity across volumes " - "for each voxel."); + +Example("Calculate the mean b=0 image from a 4D DWI series", + "dwiextract dwi.mif - -bzero | mrmath - mean mean_bzero.mif -axis 3", + "The dwiextract command extracts all volumes for which the b-value is " + "(approximately) zero; the resulting 4D image can then be provided to " + "the mrmath command to calculate the mean intensity across volumes " + "for each voxel."); ARGUMENTS - + Argument ("input", "the input DW image.").type_image_in () - + Argument ("output", "the output image (diffusion-weighted volumes by default).").type_image_out (); + +Argument("input", "the input DW image.").type_image_in() + + Argument("output", "the output image (diffusion-weighted volumes by default).").type_image_out(); OPTIONS - + Option ("bzero", "Output b=0 volumes (instead of the diffusion weighted volumes, if -singleshell is not specified).") - + Option ("no_bzero", "Output only non b=0 volumes (default, if -singleshell is not specified).") - + Option ("singleshell", "Force a single-shell (single non b=0 shell) output. This will include b=0 volumes, if present. Use with -bzero to enforce presence of b=0 volumes (error if not present) or with -no_bzero to exclude them.") - + DWI::GradImportOptions() - + DWI::ShellsOption - + DWI::GradExportOptions() - + PhaseEncoding::ImportOptions - + PhaseEncoding::SelectOptions - + Stride::Options; + +Option("bzero", + "Output b=0 volumes (instead of the diffusion weighted volumes, if -singleshell is not specified).") + + Option("no_bzero", "Output only non b=0 volumes (default, if -singleshell is not specified).") + + Option("singleshell", + "Force a single-shell (single non b=0 shell) output. This will include b=0 volumes, if present. Use with " + "-bzero to enforce presence of b=0 volumes (error if not present) or with -no_bzero to exclude them.") + + DWI::GradImportOptions() + DWI::ShellsOption + DWI::GradExportOptions() + PhaseEncoding::ImportOptions + + PhaseEncoding::SelectOptions + Stride::Options; } - - - - - -void run() -{ - auto input_image = Image::open (argument[0]); +void run() { + auto input_image = Image::open(argument[0]); if (input_image.ndim() < 4) - throw Exception ("Epected input image to contain more than three dimensions"); - auto grad = DWI::get_DW_scheme (input_image); + throw Exception("Epected input image to contain more than three dimensions"); + auto grad = DWI::get_DW_scheme(input_image); // Want to support non-shell-like data if it's just a straight extraction // of all dwis or all bzeros i.e. don't initialise the Shells class vector volumes; - bool bzero = get_options ("bzero").size(); - if (get_options ("shells").size() || get_options ("singleshell").size()) { - DWI::Shells shells (grad); - shells.select_shells (get_options ("singleshell").size(),get_options ("bzero").size(),get_options ("no_bzero").size()); + bool bzero = get_options("bzero").size(); + if (get_options("shells").size() || get_options("singleshell").size()) { + DWI::Shells shells(grad); + shells.select_shells( + get_options("singleshell").size(), get_options("bzero").size(), get_options("no_bzero").size()); for (size_t s = 0; s != shells.count(); ++s) { - DEBUG ("Including data from shell b=" + str(shells[s].get_mean()) + " +- " + str(shells[s].get_stdev())); + DEBUG("Including data from shell b=" + str(shells[s].get_mean()) + " +- " + str(shells[s].get_stdev())); for (const auto v : shells[s].get_volumes()) - volumes.push_back (v); + volumes.push_back(v); } bzero = (shells.count() == 1 && shells.has_bzero()); - // If no command-line options specified, then just grab all non-b=0 volumes - // If however we are selecting volumes according to phase-encoding, and - // shells have not been explicitly selected, do NOT filter by b-value here - } else if (!get_options ("pe").size()) { - const float bzero_threshold = File::Config::get_float ("BZeroThreshold", 10.0); + // If no command-line options specified, then just grab all non-b=0 volumes + // If however we are selecting volumes according to phase-encoding, and + // shells have not been explicitly selected, do NOT filter by b-value here + } else if (!get_options("pe").size()) { + const float bzero_threshold = File::Config::get_float("BZeroThreshold", 10.0); for (ssize_t row = 0; row != grad.rows(); ++row) { - if ((bzero && (grad (row, 3) < bzero_threshold)) || (!bzero && (grad (row, 3) > bzero_threshold))) - volumes.push_back (row); + if ((bzero && (grad(row, 3) < bzero_threshold)) || (!bzero && (grad(row, 3) > bzero_threshold))) + volumes.push_back(row); } } else { // "pe" option has been provided - need to initialise list of volumes // to include all voxels, as the PE selection filters from this for (uint32_t i = 0; i != grad.rows(); ++i) - volumes.push_back (i); + volumes.push_back(i); } - auto opt = get_options ("pe"); - const auto pe_scheme = PhaseEncoding::get_scheme (input_image); + auto opt = get_options("pe"); + const auto pe_scheme = PhaseEncoding::get_scheme(input_image); if (opt.size()) { if (!pe_scheme.rows()) - throw Exception ("Cannot filter volumes by phase-encoding: No such information present"); - const auto filter = parse_floats (opt[0][0]); + throw Exception("Cannot filter volumes by phase-encoding: No such information present"); + const auto filter = parse_floats(opt[0][0]); if (!(filter.size() == 3 || filter.size() == 4)) - throw Exception ("Phase encoding filter must be a comma-separated list of either 3 or 4 numbers"); + throw Exception("Phase encoding filter must be a comma-separated list of either 3 or 4 numbers"); vector new_volumes; for (const auto i : volumes) { bool keep = true; @@ -118,41 +111,41 @@ void run() } } if (filter.size() == 4) { - if (abs (pe_scheme(i, 3) - filter[3]) > 5e-3) + if (abs(pe_scheme(i, 3) - filter[3]) > 5e-3) keep = false; } if (keep) - new_volumes.push_back (i); + new_volumes.push_back(i); } - std::swap (volumes, new_volumes); + std::swap(volumes, new_volumes); } if (volumes.empty()) { auto type = (bzero) ? "b=0" : "dwi"; - throw Exception ("No " + str(type) + " volumes present"); + throw Exception("No " + str(type) + " volumes present"); } - std::sort (volumes.begin(), volumes.end()); + std::sort(volumes.begin(), volumes.end()); - Header header (input_image); - Stride::set_from_command_line (header); - header.size (3) = volumes.size(); + Header header(input_image); + Stride::set_from_command_line(header); + header.size(3) = volumes.size(); - Eigen::MatrixXd new_grad (volumes.size(), grad.cols()); + Eigen::MatrixXd new_grad(volumes.size(), grad.cols()); for (size_t i = 0; i < volumes.size(); i++) - new_grad.row (i) = grad.row (volumes[i]); - DWI::set_DW_scheme (header, new_grad); + new_grad.row(i) = grad.row(volumes[i]); + DWI::set_DW_scheme(header, new_grad); if (pe_scheme.rows()) { - Eigen::MatrixXd new_scheme (volumes.size(), pe_scheme.cols()); + Eigen::MatrixXd new_scheme(volumes.size(), pe_scheme.cols()); for (size_t i = 0; i != volumes.size(); ++i) - new_scheme.row(i) = pe_scheme.row (volumes[i]); - PhaseEncoding::set_scheme (header, new_scheme); + new_scheme.row(i) = pe_scheme.row(volumes[i]); + PhaseEncoding::set_scheme(header, new_scheme); } - auto output_image = Image::create (argument[1], header); - DWI::export_grad_commandline (header); + auto output_image = Image::create(argument[1], header); + DWI::export_grad_commandline(header); - auto input_volumes = Adapter::make (input_image, 3, volumes); - threaded_copy_with_progress_message ("extracting volumes", input_volumes, output_image); + auto input_volumes = Adapter::make(input_image, 3, volumes); + threaded_copy_with_progress_message("extracting volumes", input_volumes, output_image); } diff --git a/cmd/fixel2peaks.cpp b/cmd/fixel2peaks.cpp index 5757f31568..3fa92a1af5 100644 --- a/cmd/fixel2peaks.cpp +++ b/cmd/fixel2peaks.cpp @@ -30,121 +30,116 @@ using namespace App; using Fixel::index_type; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Convert data in the fixel directory format into a 4D image of 3-vectors"; DESCRIPTION - + "If a fixel data file is provided as input, then the 3-vectors in the " - "output image will be scaled based on the data in that file. If the input " - "is instead the fixel directory, or the index or directions file, then " - "all output 3-vectors will possess unit norm." + +"If a fixel data file is provided as input, then the 3-vectors in the " + "output image will be scaled based on the data in that file. If the input " + "is instead the fixel directory, or the index or directions file, then " + "all output 3-vectors will possess unit norm." - + Fixel::format_description; + + Fixel::format_description; ARGUMENTS - + Argument ("in", "the input fixel information").type_various () - + Argument ("out", "the output peaks image").type_image_out (); + +Argument("in", "the input fixel information").type_various() + + Argument("out", "the output peaks image").type_image_out(); OPTIONS - + Option ("number", "maximum number of fixels in each voxel (default: based on input data)") - + Argument ("value").type_integer(1) - + Option ("nan", "fill excess peak data with NaNs rather than zeroes"); - + +Option("number", "maximum number of fixels in each voxel (default: based on input data)") + + Argument("value").type_integer(1) + Option("nan", "fill excess peak data with NaNs rather than zeroes"); } - -void run () -{ +void run() { Header index_header, directions_header, data_header; try { - Header input_header = Header::open (argument[0]); - if (Fixel::is_index_image (input_header)) { - index_header = std::move (input_header); - directions_header = Fixel::find_directions_header (Path::dirname (argument[0])); - } else if (Fixel::is_directions_file (input_header)) { - index_header = Fixel::find_index_header (Path::dirname (argument[0])); - directions_header = std::move (input_header); - } else if (Fixel::is_data_file (input_header)) { - index_header = Fixel::find_index_header (Path::dirname (argument[0])); - directions_header = Fixel::find_directions_header (Path::dirname (argument[0])); - data_header = std::move (input_header); - Fixel::check_fixel_size (index_header, data_header); + Header input_header = Header::open(argument[0]); + if (Fixel::is_index_image(input_header)) { + index_header = std::move(input_header); + directions_header = Fixel::find_directions_header(Path::dirname(argument[0])); + } else if (Fixel::is_directions_file(input_header)) { + index_header = Fixel::find_index_header(Path::dirname(argument[0])); + directions_header = std::move(input_header); + } else if (Fixel::is_data_file(input_header)) { + index_header = Fixel::find_index_header(Path::dirname(argument[0])); + directions_header = Fixel::find_directions_header(Path::dirname(argument[0])); + data_header = std::move(input_header); + Fixel::check_fixel_size(index_header, data_header); } else { - throw Exception ("Input image not recognised as part of fixel format"); + throw Exception("Input image not recognised as part of fixel format"); } - } catch (Exception& e_asimage) { + } catch (Exception &e_asimage) { try { - if (!Path::is_dir (argument[0])) - throw Exception ("Input path is not a directory"); - index_header = Fixel::find_index_header (argument[0]); - directions_header = Fixel::find_directions_header (argument[0]); - } catch (Exception& e_asdir) { - Exception e ("Could not locate fixel data based on input string \"" + argument[0] + "\""); - e.push_back ("Error when interpreting as image: "); + if (!Path::is_dir(argument[0])) + throw Exception("Input path is not a directory"); + index_header = Fixel::find_index_header(argument[0]); + directions_header = Fixel::find_directions_header(argument[0]); + } catch (Exception &e_asdir) { + Exception e("Could not locate fixel data based on input string \"" + argument[0] + "\""); + e.push_back("Error when interpreting as image: "); for (size_t i = 0; i != e_asimage.num(); ++i) - e.push_back (" " + e_asimage[i]); - e.push_back ("Error when interpreting as fixel directory: "); + e.push_back(" " + e_asimage[i]); + e.push_back("Error when interpreting as fixel directory: "); for (size_t i = 0; i != e_asdir.num(); ++i) - e.push_back (" " + e_asdir[i]); + e.push_back(" " + e_asdir[i]); throw e; } } - Image index_image (index_header.get_image()); - Image directions_image (directions_header.get_image()); + Image index_image(index_header.get_image()); + Image directions_image(directions_header.get_image()); Image data_image; if (data_header.valid()) data_image = data_header.get_image(); - auto opt = get_options ("number"); + auto opt = get_options("number"); index_type max_fixel_count = 0; if (opt.size()) { max_fixel_count = opt[0][0]; } else { - for (auto l = Loop (index_image, 0, 3) (index_image); l; ++l) - max_fixel_count = std::max (max_fixel_count, index_type (index_image.value())); - INFO ("Maximum number of fixels in any given voxel: " + str(max_fixel_count)); + for (auto l = Loop(index_image, 0, 3)(index_image); l; ++l) + max_fixel_count = std::max(max_fixel_count, index_type(index_image.value())); + INFO("Maximum number of fixels in any given voxel: " + str(max_fixel_count)); } - Header out_header (index_header); + Header out_header(index_header); out_header.datatype() = DataType::Float32; out_header.datatype().set_byte_order_native(); out_header.size(3) = 3 * max_fixel_count; - out_header.name() = std::string (argument[1]); - Image out_image (Image::create (argument[1], out_header)); + out_header.name() = std::string(argument[1]); + Image out_image(Image::create(argument[1], out_header)); - const float fill = get_options ("nan").size() ? NaN : 0.0f; + const float fill = get_options("nan").size() ? NaN : 0.0f; if (data_image.valid()) { - for (auto l = Loop ("converting fixel data file to peaks image", index_image, 0, 3) (index_image, out_image); l; ++l) { - out_image.index (3) = 0; - for (auto f = Fixel::Loop (index_image) (directions_image, data_image); f && out_image.index(3) < out_image.size(3); ++f) { + for (auto l = Loop("converting fixel data file to peaks image", index_image, 0, 3)(index_image, out_image); l; + ++l) { + out_image.index(3) = 0; + for (auto f = Fixel::Loop(index_image)(directions_image, data_image); f && out_image.index(3) < out_image.size(3); + ++f) { for (size_t axis = 0; axis != 3; ++axis) { directions_image.index(1) = axis; out_image.value() = data_image.value() * directions_image.value(); ++out_image.index(3); } } - for (; out_image.index(3) != out_image.size (3); ++out_image.index(3)) + for (; out_image.index(3) != out_image.size(3); ++out_image.index(3)) out_image.value() = fill; } } else { - for (auto l = Loop ("converting fixels to peaks image", index_image, 0, 3) (index_image, out_image); l; ++l) { - out_image.index (3) = 0; - for (auto f = Fixel::Loop (index_image) (directions_image); f && out_image.index(3) < out_image.size(3); ++f) { + for (auto l = Loop("converting fixels to peaks image", index_image, 0, 3)(index_image, out_image); l; ++l) { + out_image.index(3) = 0; + for (auto f = Fixel::Loop(index_image)(directions_image); f && out_image.index(3) < out_image.size(3); ++f) { for (directions_image.index(1) = 0; directions_image.index(1) != 3; ++directions_image.index(1)) { out_image.value() = directions_image.value(); ++out_image.index(3); } } - for (; out_image.index(3) != out_image.size (3); ++out_image.index(3)) + for (; out_image.index(3) != out_image.size(3); ++out_image.index(3)) out_image.value() = fill; } } } - diff --git a/cmd/fixel2sh.cpp b/cmd/fixel2sh.cpp index 6cf38eba8d..d0005afba9 100644 --- a/cmd/fixel2sh.cpp +++ b/cmd/fixel2sh.cpp @@ -31,68 +31,66 @@ using namespace App; using Fixel::index_type; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au) & David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Convert a fixel-based sparse-data image into an spherical harmonic image"; DESCRIPTION - + "This command generates spherical harmonic data from fixels " - "that can be visualised using the ODF tool in MRview. The output ODF lobes " - "are scaled according to the values in the input fixel image." + +"This command generates spherical harmonic data from fixels " + "that can be visualised using the ODF tool in MRview. The output ODF lobes " + "are scaled according to the values in the input fixel image." - + Math::SH::encoding_description + + Math::SH::encoding_description - + Fixel::format_description; + + Fixel::format_description; ARGUMENTS - + Argument ("fixel_in", "the input fixel data file.").type_image_in () - + Argument ("sh_out", "the output sh image.").type_image_out (); + +Argument("fixel_in", "the input fixel data file.").type_image_in() + + Argument("sh_out", "the output sh image.").type_image_out(); OPTIONS - + Option ("lmax", "set the maximum harmonic order for the output series (Default: 8)") - + Argument ("order").type_integer (0, 30); + +Option("lmax", "set the maximum harmonic order for the output series (Default: 8)") + + Argument("order").type_integer(0, 30); } +void run() { + auto in_data_image = Fixel::open_fixel_data_file(argument[0]); -void run () -{ - auto in_data_image = Fixel::open_fixel_data_file (argument[0]); - - Header in_index_header = Fixel::find_index_header (Fixel::get_fixel_directory (argument[0])); - auto in_index_image =in_index_header.get_image(); - auto in_directions_image = Fixel::find_directions_header (Fixel::get_fixel_directory (argument[0])).get_image().with_direct_io(); + Header in_index_header = Fixel::find_index_header(Fixel::get_fixel_directory(argument[0])); + auto in_index_image = in_index_header.get_image(); + auto in_directions_image = + Fixel::find_directions_header(Fixel::get_fixel_directory(argument[0])).get_image().with_direct_io(); size_t lmax = 8; - auto opt = get_options ("lmax"); + auto opt = get_options("lmax"); if (opt.size()) lmax = opt[0][0]; - const size_t n_sh_coeff = Math::SH::NforL (lmax); - Math::SH::aPSF aPSF (lmax); + const size_t n_sh_coeff = Math::SH::NforL(lmax); + Math::SH::aPSF aPSF(lmax); - Header out_header (in_index_header); + Header out_header(in_index_header); out_header.datatype() = DataType::Float32; out_header.datatype().set_byte_order_native(); out_header.ndim() = 4; - out_header.size (3) = n_sh_coeff; + out_header.size(3) = n_sh_coeff; - auto sh_image = Image::create (argument[1], out_header); + auto sh_image = Image::create(argument[1], out_header); vector sh_values; Eigen::Matrix apsf_values; - for (auto l1 = Loop ("converting fixel image to spherical harmonic image", in_index_image) (in_index_image, sh_image); l1; ++l1) { - sh_values.assign (n_sh_coeff, 0.0); - for (auto f = Fixel::Loop (in_index_image) (in_directions_image, in_data_image); f; ++f) { - apsf_values = aPSF (apsf_values, in_directions_image.row(1)); + for (auto l1 = Loop("converting fixel image to spherical harmonic image", in_index_image)(in_index_image, sh_image); + l1; + ++l1) { + sh_values.assign(n_sh_coeff, 0.0); + for (auto f = Fixel::Loop(in_index_image)(in_directions_image, in_data_image); f; ++f) { + apsf_values = aPSF(apsf_values, in_directions_image.row(1)); const default_type scale_factor = in_data_image.value(); for (size_t i = 0; i != n_sh_coeff; ++i) sh_values[i] += apsf_values[i] * scale_factor; } - for (auto l2 = Loop (3) (sh_image); l2; ++l2) + for (auto l2 = Loop(3)(sh_image); l2; ++l2) sh_image.value() = sh_values[sh_image.index(3)]; } } - diff --git a/cmd/fixel2tsf.cpp b/cmd/fixel2tsf.cpp index 0d5c9fefec..876c87e6aa 100644 --- a/cmd/fixel2tsf.cpp +++ b/cmd/fixel2tsf.cpp @@ -14,10 +14,10 @@ * For more details, see http://www.mrtrix.org/. */ +#include "algo/loop.h" #include "command.h" #include "image.h" #include "progressbar.h" -#include "algo/loop.h" #include "fixel/fixel.h" #include "fixel/helpers.h" @@ -29,92 +29,86 @@ #include "dwi/tractography/mapping/loader.h" #include "dwi/tractography/mapping/mapper.h" - - using namespace MR; using namespace App; using Fixel::index_type; - #define DEFAULT_ANGULAR_THRESHOLD 45.0 - - -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Map fixel values to a track scalar file based on an input tractogram"; DESCRIPTION - + "This command is useful for visualising all brain fixels (e.g. the output from fixelcfestats) in 3D." + +"This command is useful for visualising all brain fixels (e.g. the output from fixelcfestats) in 3D." - + Fixel::format_description; + + Fixel::format_description; ARGUMENTS - + Argument ("fixel_in", "the input fixel data file (within the fixel directory)").type_image_in () - + Argument ("tracks", "the input track file ").type_tracks_in () - + Argument ("tsf", "the output track scalar file").type_file_out (); - + +Argument("fixel_in", "the input fixel data file (within the fixel directory)").type_image_in() + + Argument("tracks", "the input track file ").type_tracks_in() + + Argument("tsf", "the output track scalar file").type_file_out(); OPTIONS - + Option ("angle", "the max anglular threshold for computing correspondence " - "between a fixel direction and track tangent " - "(default = " + str(DEFAULT_ANGULAR_THRESHOLD, 2) + " degrees)") - + Argument ("value").type_float (0.001, 90.0); - + +Option("angle", + "the max anglular threshold for computing correspondence " + "between a fixel direction and track tangent " + "(default = " + + str(DEFAULT_ANGULAR_THRESHOLD, 2) + " degrees)") + + Argument("value").type_float(0.001, 90.0); } using SetVoxelDir = DWI::Tractography::Mapping::SetVoxelDir; - -void run () -{ - auto in_data_image = Fixel::open_fixel_data_file (argument[0]); +void run() { + auto in_data_image = Fixel::open_fixel_data_file(argument[0]); if (in_data_image.size(2) != 1) - throw Exception ("Only a single scalar value for each fixel can be output as a track scalar file, " - "therefore the input fixel data file must have dimension Nx1x1"); + throw Exception("Only a single scalar value for each fixel can be output as a track scalar file, " + "therefore the input fixel data file must have dimension Nx1x1"); - Header in_index_header = Fixel::find_index_header (Fixel::get_fixel_directory (argument[0])); + Header in_index_header = Fixel::find_index_header(Fixel::get_fixel_directory(argument[0])); auto in_index_image = in_index_header.get_image(); - auto in_directions_image = Fixel::find_directions_header (Fixel::get_fixel_directory (argument[0])).get_image().with_direct_io(); + auto in_directions_image = + Fixel::find_directions_header(Fixel::get_fixel_directory(argument[0])).get_image().with_direct_io(); DWI::Tractography::Properties properties; - DWI::Tractography::Reader reader (argument[1], properties); - properties.comments.push_back ("Created using fixel2tsf"); - properties.comments.push_back ("Source fixel image: " + Path::basename (argument[0])); - properties.comments.push_back ("Source track file: " + Path::basename (argument[1])); + DWI::Tractography::Reader reader(argument[1], properties); + properties.comments.push_back("Created using fixel2tsf"); + properties.comments.push_back("Source fixel image: " + Path::basename(argument[0])); + properties.comments.push_back("Source track file: " + Path::basename(argument[1])); - DWI::Tractography::ScalarWriter tsf_writer (argument[2], properties); + DWI::Tractography::ScalarWriter tsf_writer(argument[2], properties); - float angular_threshold = get_option_value ("angle", DEFAULT_ANGULAR_THRESHOLD); - const float angular_threshold_dp = cos (angular_threshold * (Math::pi / 180.0)); + float angular_threshold = get_option_value("angle", DEFAULT_ANGULAR_THRESHOLD); + const float angular_threshold_dp = cos(angular_threshold * (Math::pi / 180.0)); - const size_t num_tracks = properties["count"].empty() ? 0 : to (properties["count"]); + const size_t num_tracks = properties["count"].empty() ? 0 : to(properties["count"]); - DWI::Tractography::Mapping::TrackMapperBase mapper (in_index_image); - mapper.set_use_precise_mapping (true); + DWI::Tractography::Mapping::TrackMapperBase mapper(in_index_image); + mapper.set_use_precise_mapping(true); - ProgressBar progress ("mapping fixel values to streamline points", num_tracks); + ProgressBar progress("mapping fixel values to streamline points", num_tracks); DWI::Tractography::Streamline tck; DWI::Tractography::TrackScalar scalars; - const Transform transform (in_index_image); + const Transform transform(in_index_image); Eigen::Vector3d voxel_pos; - while (reader (tck)) { + while (reader(tck)) { SetVoxelDir dixels; - mapper (tck, dixels); + mapper(tck, dixels); scalars.clear(); - scalars.set_index (tck.get_index()); - scalars.resize (tck.size(), 0.0f); + scalars.set_index(tck.get_index()); + scalars.resize(tck.size(), 0.0f); for (size_t p = 0; p < tck.size(); ++p) { - voxel_pos = transform.scanner2voxel * tck[p].cast (); + voxel_pos = transform.scanner2voxel * tck[p].cast(); for (SetVoxelDir::const_iterator d = dixels.begin(); d != dixels.end(); ++d) { - if ((int)round(voxel_pos[0]) == (*d)[0] && (int)round(voxel_pos[1]) == (*d)[1] && (int)round(voxel_pos[2]) == (*d)[2]) { - assign_pos_of (*d).to (in_index_image); + if ((int)round(voxel_pos[0]) == (*d)[0] && (int)round(voxel_pos[1]) == (*d)[1] && + (int)round(voxel_pos[2]) == (*d)[2]) { + assign_pos_of(*d).to(in_index_image); Eigen::Vector3f dir = d->get_dir().cast(); dir.normalize(); float largest_dp = 0.0f; @@ -127,7 +121,7 @@ void run () for (size_t fixel = 0; fixel < num_fixels_in_voxel; ++fixel) { in_directions_image.index(0) = offset + fixel; - const float dp = abs (dir.dot (Eigen::Vector3f (in_directions_image.row(1)))); + const float dp = abs(dir.dot(Eigen::Vector3f(in_directions_image.row(1)))); if (dp > largest_dp) { largest_dp = dp; closest_fixel_index = fixel; @@ -136,7 +130,7 @@ void run () if (largest_dp > angular_threshold_dp) { in_data_image.index(0) = offset + closest_fixel_index; const float value = in_data_image.value(); - if (std::isfinite (value)) + if (std::isfinite(value)) scalars[p] = in_data_image.value(); else scalars[p] = 0.0f; @@ -147,8 +141,7 @@ void run () } } } - tsf_writer (scalars); + tsf_writer(scalars); progress++; } } - diff --git a/cmd/fixel2voxel.cpp b/cmd/fixel2voxel.cpp index 1fa159679e..ec15a95093 100644 --- a/cmd/fixel2voxel.cpp +++ b/cmd/fixel2voxel.cpp @@ -18,10 +18,10 @@ #include "command.h" #include "progressbar.h" -#include "image.h" #include "algo/loop.h" #include "algo/threaded_loop.h" #include "formats/mrtrix_utils.h" +#include "image.h" #include "dwi/tractography/file.h" #include "dwi/tractography/scalar_file.h" @@ -37,492 +37,430 @@ using namespace App; using Fixel::index_type; - -const char* operations[] = { - "mean", - "sum", - "product", - "min", - "max", - "absmax", - "magmax", - "count", - "complexity", - "sf", - "dec_unit", - "dec_scaled", - "none", - nullptr -}; - - -void usage () -{ +const char *operations[] = {"mean", + "sum", + "product", + "min", + "max", + "absmax", + "magmax", + "count", + "complexity", + "sf", + "dec_unit", + "dec_scaled", + "none", + nullptr}; + +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au) & David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Convert a fixel-based sparse-data image into some form of scalar image"; DESCRIPTION - + "Fixel data can be reduced to voxel data in a number of ways:" - + "- Some statistic computed across all fixel values within a voxel: mean, sum, product, min, max, absmax, magmax" - + "- The number of fixels in each voxel: count" - + "- Some measure of crossing-fibre organisation: complexity, sf ('single-fibre')" - + "- A 4D directionally-encoded colour image: dec_unit, dec_scaled" - + "- A 4D image containing all fixel data values in each voxel unmodified: none" - - + "The -weighted option deals with the case where there is some per-fixel metric of interest " - "that you wish to collapse into a single scalar measure per voxel, but each fixel possesses " - "a different volume, and you wish for those fixels with greater volume to have a greater " - "influence on the calculation than fixels with lesser volume. For instance, when estimating " - "a voxel-based measure of mean axon diameter from per-fixel mean axon diameters, a fixel's " - "mean axon diameter should be weigthed by its relative volume within the voxel in the " - "calculation of that voxel mean." - - + Fixel::format_description; + +"Fixel data can be reduced to voxel data in a number of ways:" + + "- Some statistic computed across all fixel values within a voxel: mean, sum, product, min, max, absmax, magmax" + + "- The number of fixels in each voxel: count" + + "- Some measure of crossing-fibre organisation: complexity, sf ('single-fibre')" + + "- A 4D directionally-encoded colour image: dec_unit, dec_scaled" + + "- A 4D image containing all fixel data values in each voxel unmodified: none" + + + "The -weighted option deals with the case where there is some per-fixel metric of interest " + "that you wish to collapse into a single scalar measure per voxel, but each fixel possesses " + "a different volume, and you wish for those fixels with greater volume to have a greater " + "influence on the calculation than fixels with lesser volume. For instance, when estimating " + "a voxel-based measure of mean axon diameter from per-fixel mean axon diameters, a fixel's " + "mean axon diameter should be weigthed by its relative volume within the voxel in the " + "calculation of that voxel mean." + + + Fixel::format_description; REFERENCES - + "* Reference for 'complexity' operation:\n" - "Riffert, T. W.; Schreiber, J.; Anwander, A. & Knosche, T. R. " - "Beyond Fractional Anisotropy: Extraction of bundle-specific structural metrics from crossing fibre models. " - "NeuroImage, 2014, 100, 176-191"; + +"* Reference for 'complexity' operation:\n" + "Riffert, T. W.; Schreiber, J.; Anwander, A. & Knosche, T. R. " + "Beyond Fractional Anisotropy: Extraction of bundle-specific structural metrics from crossing fibre models. " + "NeuroImage, 2014, 100, 176-191"; ARGUMENTS - + Argument ("fixel_in", "the input fixel data file").type_image_in () - + Argument ("operation", "the operation to apply, one of: " + join(operations, ", ") + ".").type_choice (operations) - + Argument ("image_out", "the output scalar image.").type_image_out (); + +Argument("fixel_in", "the input fixel data file").type_image_in() + + Argument("operation", "the operation to apply, one of: " + join(operations, ", ") + ".").type_choice(operations) + + Argument("image_out", "the output scalar image.").type_image_out(); OPTIONS - + Option ("number", "use only the largest N fixels in calculation of the voxel-wise statistic; " - "in the case of operation \"none\", output only the largest N fixels in each voxel.") - + Argument ("N").type_integer(1) - - + Option ("fill", "for \"none\" operation, specify the value to fill when number of fixels is fewer than the maximum (default: 0.0)") - + Argument ("value").type_float() - - + Option ("weighted", "weight the contribution of each fixel to the per-voxel result according to its volume.") - + Argument ("fixel_in").type_image_in (); - + +Option("number", + "use only the largest N fixels in calculation of the voxel-wise statistic; " + "in the case of operation \"none\", output only the largest N fixels in each voxel.") + + Argument("N").type_integer(1) + + + Option("fill", + "for \"none\" operation, specify the value to fill when number of fixels is fewer than the maximum " + "(default: 0.0)") + + Argument("value").type_float() + + + Option("weighted", "weight the contribution of each fixel to the per-voxel result according to its volume.") + + Argument("fixel_in").type_image_in(); } - - using FixelIndexType = Image; using FixelDataType = Image; - - -struct set_offset { - FORCE_INLINE set_offset (index_type offset) : offset (offset) { } - template - FORCE_INLINE void operator() (DataType& data) { data.index(0) = offset; } +struct set_offset { + FORCE_INLINE set_offset(index_type offset) : offset(offset) {} + template FORCE_INLINE void operator()(DataType &data) { data.index(0) = offset; } index_type offset; }; -struct inc_fixel { - template - FORCE_INLINE void operator() (DataType& data) { ++data.index(0); } +struct inc_fixel { + template FORCE_INLINE void operator()(DataType &data) { ++data.index(0); } }; - - -struct LoopFixelsInVoxelWithMax { +struct LoopFixelsInVoxelWithMax { const index_type num_fixels; const index_type max_fixels; const index_type offset; - template - struct Run { + template struct Run { const index_type num_fixels; const index_type max_fixels; const index_type offset; index_type fixel_index; - const std::tuple data; - FORCE_INLINE Run (const index_type num_fixels, const index_type max_fixels, const index_type offset, const std::tuple& data) : - num_fixels (num_fixels), max_fixels (max_fixels), offset (offset), fixel_index (0), data (data) { - MR::apply (set_offset (offset), data); + const std::tuple data; + FORCE_INLINE Run(const index_type num_fixels, + const index_type max_fixels, + const index_type offset, + const std::tuple &data) + : num_fixels(num_fixels), max_fixels(max_fixels), offset(offset), fixel_index(0), data(data) { + MR::apply(set_offset(offset), data); } FORCE_INLINE operator bool() const { return max_fixels ? (fixel_index < max_fixels) : (fixel_index < num_fixels); } - FORCE_INLINE void operator++() { if (!padding()) MR::apply (inc_fixel (), data); ++fixel_index; } + FORCE_INLINE void operator++() { + if (!padding()) + MR::apply(inc_fixel(), data); + ++fixel_index; + } FORCE_INLINE void operator++(int) { operator++(); } FORCE_INLINE bool padding() const { return (max_fixels && fixel_index >= num_fixels); } FORCE_INLINE index_type count() const { return max_fixels ? max_fixels : num_fixels; } }; - template - FORCE_INLINE Run operator() (DataType&... data) const { return { num_fixels, max_fixels, offset, std::tie (data...) }; } - + template FORCE_INLINE Run operator()(DataType &...data) const { + return {num_fixels, max_fixels, offset, std::tie(data...)}; + } }; +class Base { +public: + Base(FixelDataType &data, const index_type max_fixels, const bool pad = false, const float pad_value = 0.0) + : data(data), max_fixels(max_fixels), pad(pad), pad_value(pad_value) {} + +protected: + FORCE_INLINE LoopFixelsInVoxelWithMax Loop(FixelIndexType &index) { + index.index(3) = 0; + const index_type num_fixels = index.value(); + index.index(3) = 1; + const index_type offset = index.value(); + return {num_fixels, max_fixels, offset}; + } - -class Base -{ - public: - Base (FixelDataType& data, const index_type max_fixels, const bool pad = false, const float pad_value = 0.0) : - data (data), - max_fixels (max_fixels), - pad (pad), - pad_value (pad_value) { } - - protected: - FORCE_INLINE LoopFixelsInVoxelWithMax - Loop (FixelIndexType& index) { - index.index(3) = 0; - const index_type num_fixels = index.value(); - index.index(3) = 1; - const index_type offset = index.value(); - return { num_fixels, max_fixels, offset }; - } - - FixelDataType data; - const index_type max_fixels; - const bool pad; - const float pad_value; - + FixelDataType data; + const index_type max_fixels; + const bool pad; + const float pad_value; }; +class Mean : protected Base { +public: + Mean(FixelDataType &data, const index_type max_fixels, FixelDataType &vol) : Base(data, max_fixels), vol(vol) {} -class Mean : protected Base -{ - public: - Mean (FixelDataType& data, const index_type max_fixels, FixelDataType& vol) : - Base (data, max_fixels), - vol (vol) {} - - void operator() (FixelIndexType& index, Image& out) - { - default_type sum = 0.0; - default_type sum_volumes = 0.0; - if (vol.valid()) { - for (auto f = Base::Loop (index) (data, vol); f; ++f) { - if (!f.padding()) { - sum += data.value() * vol.value(); - sum_volumes += vol.value(); - } + void operator()(FixelIndexType &index, Image &out) { + default_type sum = 0.0; + default_type sum_volumes = 0.0; + if (vol.valid()) { + for (auto f = Base::Loop(index)(data, vol); f; ++f) { + if (!f.padding()) { + sum += data.value() * vol.value(); + sum_volumes += vol.value(); } - out.value() = sum_volumes ? (sum / sum_volumes) : 0.0; - } else { - for (auto f = Base::Loop (index) (data); f; ++f) { - if (!f.padding()) { - sum += data.value(); - sum_volumes += 1.0; - } + } + out.value() = sum_volumes ? (sum / sum_volumes) : 0.0; + } else { + for (auto f = Base::Loop(index)(data); f; ++f) { + if (!f.padding()) { + sum += data.value(); + sum_volumes += 1.0; } - out.value() = sum_volumes ? (sum / sum_volumes) : 0.0; } + out.value() = sum_volumes ? (sum / sum_volumes) : 0.0; } - protected: - FixelDataType vol; -}; + } +protected: + FixelDataType vol; +}; -class Sum : protected Base -{ - public: - Sum (FixelDataType& data, const index_type max_fixels, FixelDataType& vol) : - Base (data, max_fixels), - vol (vol) {} +class Sum : protected Base { +public: + Sum(FixelDataType &data, const index_type max_fixels, FixelDataType &vol) : Base(data, max_fixels), vol(vol) {} - void operator() (FixelIndexType& index, Image& out) - { - if (vol.valid()) { - for (auto f = Base::Loop (index) (data, vol); f; ++f) { - if (!f.padding()) - out.value() += data.value() * vol.value(); - } - } else { - for (auto f = Base::Loop (index) (data); f; ++f) { - if (!f.padding()) - out.value() += data.value(); - } + void operator()(FixelIndexType &index, Image &out) { + if (vol.valid()) { + for (auto f = Base::Loop(index)(data, vol); f; ++f) { + if (!f.padding()) + out.value() += data.value() * vol.value(); + } + } else { + for (auto f = Base::Loop(index)(data); f; ++f) { + if (!f.padding()) + out.value() += data.value(); } } - protected: - FixelDataType vol; -}; + } +protected: + FixelDataType vol; +}; -class Product : protected Base -{ - public: - Product (FixelDataType& data, const index_type max_fixels) : - Base (data, max_fixels) { } +class Product : protected Base { +public: + Product(FixelDataType &data, const index_type max_fixels) : Base(data, max_fixels) {} - void operator() (FixelIndexType& index, Image& out) - { - index.index(3) = 0; - index_type num_fixels = index.value(); - if (!num_fixels) { - out.value() = 0.0; - return; - } - index.index(3) = 1; - index_type offset = index.value(); - data.index(0) = offset; - out.value() = data.value(); - num_fixels = max_fixels ? std::min (max_fixels, num_fixels) : num_fixels; - for (index_type f = 1; f != num_fixels; ++f) { - data.index(0)++; - out.value() *= data.value(); - } + void operator()(FixelIndexType &index, Image &out) { + index.index(3) = 0; + index_type num_fixels = index.value(); + if (!num_fixels) { + out.value() = 0.0; + return; + } + index.index(3) = 1; + index_type offset = index.value(); + data.index(0) = offset; + out.value() = data.value(); + num_fixels = max_fixels ? std::min(max_fixels, num_fixels) : num_fixels; + for (index_type f = 1; f != num_fixels; ++f) { + data.index(0)++; + out.value() *= data.value(); } + } }; +class Min : protected Base { +public: + Min(FixelDataType &data, const index_type max_fixels) : Base(data, max_fixels) {} -class Min : protected Base -{ - public: - Min (FixelDataType& data, const index_type max_fixels) : - Base (data, max_fixels) { } - - void operator() (FixelIndexType& index, Image& out) - { - default_type min = std::numeric_limits::infinity(); - for (auto f = Base::Loop (index) (data); f; ++f) { - if (!f.padding() && data.value() < min) - min = data.value(); - } - out.value() = std::isfinite (min) ? min : NAN; + void operator()(FixelIndexType &index, Image &out) { + default_type min = std::numeric_limits::infinity(); + for (auto f = Base::Loop(index)(data); f; ++f) { + if (!f.padding() && data.value() < min) + min = data.value(); } + out.value() = std::isfinite(min) ? min : NAN; + } }; +class Max : protected Base { +public: + Max(FixelDataType &data, const index_type max_fixels) : Base(data, max_fixels) {} -class Max : protected Base -{ - public: - Max (FixelDataType& data, const index_type max_fixels) : - Base (data, max_fixels) { } - - void operator() (FixelIndexType& index, Image& out) - { - default_type max = -std::numeric_limits::infinity(); - for (auto f = Base::Loop (index) (data); f; ++f) { - if (!f.padding() && data.value() > max) - max = data.value(); - } - out.value() = std::isfinite (max) ? max : NAN; + void operator()(FixelIndexType &index, Image &out) { + default_type max = -std::numeric_limits::infinity(); + for (auto f = Base::Loop(index)(data); f; ++f) { + if (!f.padding() && data.value() > max) + max = data.value(); } + out.value() = std::isfinite(max) ? max : NAN; + } }; +class AbsMax : protected Base { +public: + AbsMax(FixelDataType &data, const index_type max_fixels) : Base(data, max_fixels) {} -class AbsMax : protected Base -{ - public: - AbsMax (FixelDataType& data, const index_type max_fixels) : - Base (data, max_fixels) { } - - void operator() (FixelIndexType& index, Image& out) - { - default_type absmax = -std::numeric_limits::infinity(); - for (auto f = Base::Loop (index) (data); f; ++f) { - if (!f.padding() && abs (float(data.value())) > absmax) - absmax = abs (float(data.value())); - } - out.value() = std::isfinite (absmax) ? absmax : 0.0; + void operator()(FixelIndexType &index, Image &out) { + default_type absmax = -std::numeric_limits::infinity(); + for (auto f = Base::Loop(index)(data); f; ++f) { + if (!f.padding() && abs(float(data.value())) > absmax) + absmax = abs(float(data.value())); } + out.value() = std::isfinite(absmax) ? absmax : 0.0; + } }; +class MagMax : protected Base { +public: + MagMax(FixelDataType &data, const index_type num_fixels) : Base(data, num_fixels) {} -class MagMax : protected Base -{ - public: - MagMax (FixelDataType& data, const index_type num_fixels) : - Base (data, num_fixels) { } + void operator()(FixelIndexType &index, Image &out) { + default_type magmax = 0.0; + for (auto f = Base::Loop(index)(data); f; ++f) { + if (!f.padding() && abs(float(data.value())) > abs(magmax)) + magmax = data.value(); + } + out.value() = std::isfinite(magmax) ? magmax : 0.0; + } +}; - void operator() (FixelIndexType& index, Image& out) - { - default_type magmax = 0.0; - for (auto f = Base::Loop (index) (data); f; ++f) { - if (!f.padding() && abs (float(data.value())) > abs (magmax)) - magmax = data.value(); +class Complexity : protected Base { +public: + Complexity(FixelDataType &data, const index_type max_fixels) : Base(data, max_fixels) {} + + void operator()(FixelIndexType &index, Image &out) { + index.index(3) = 0; + index_type num_fixels = index.value(); + num_fixels = max_fixels ? std::min(num_fixels, max_fixels) : num_fixels; + if (num_fixels <= 1) { + out.value() = 0.0; + return; + } + default_type max = 0.0; + default_type sum = 0.0; + for (auto f = Base::Loop(index)(data); f; ++f) { + if (!f.padding()) { + max = std::max(max, default_type(data.value())); + sum += data.value(); } - out.value() = std::isfinite (magmax) ? magmax : 0.0; } + out.value() = (default_type(num_fixels) / default_type(num_fixels - 1.0)) * (1.0 - (max / sum)); + } }; - -class Complexity : protected Base -{ - public: - Complexity (FixelDataType& data, const index_type max_fixels) : - Base (data, max_fixels) { } - - void operator() (FixelIndexType& index, Image& out) - { - index.index(3) = 0; - index_type num_fixels = index.value(); - num_fixels = max_fixels ? std::min (num_fixels, max_fixels) : num_fixels; - if (num_fixels <= 1) { - out.value() = 0.0; - return; - } - default_type max = 0.0; - default_type sum = 0.0; - for (auto f = Base::Loop (index) (data); f; ++f) { - if (!f.padding()) { - max = std::max (max, default_type(data.value())); - sum += data.value(); - } +class SF : protected Base { +public: + SF(FixelDataType &data, const index_type max_fixels) : Base(data, max_fixels) {} + + void operator()(Image &index, FixelDataType &out) { + default_type max = 0.0; + default_type sum = 0.0; + for (auto f = Base::Loop(index)(data); f; ++f) { + if (!f.padding()) { + max = std::max(max, default_type(data.value())); + sum += data.value(); } - out.value() = (default_type(num_fixels) / default_type(num_fixels-1.0)) * (1.0 - (max / sum)); } + out.value() = sum ? (max / sum) : 0.0; + } }; - -class SF : protected Base -{ - public: - SF (FixelDataType& data, const index_type max_fixels) : - Base (data, max_fixels) { } - - void operator() (Image& index, FixelDataType& out) - { - default_type max = 0.0; - default_type sum = 0.0; - for (auto f = Base::Loop (index) (data); f; ++f) { - if (!f.padding()) { - max = std::max (max, default_type(data.value())); - sum += data.value(); - } +class DEC_unit : protected Base { +public: + DEC_unit(FixelDataType &data, const index_type max_fixels, FixelDataType &vol, Image &dir) + : Base(data, max_fixels), vol(vol), dir(dir) {} + + void operator()(Image &index, Image &out) { + Eigen::Vector3d sum_dec = {0.0, 0.0, 0.0}; + if (vol.valid()) { + for (auto f = Base::Loop(index)(data, vol, dir); f; ++f) { + if (!f.padding()) + sum_dec += + Eigen::Vector3d(abs(dir.row(1)[0]), abs(dir.row(1)[1]), abs(dir.row(1)[2])) * data.value() * vol.value(); + } + } else { + for (auto f = Base::Loop(index)(data, dir); f; ++f) { + if (!f.padding()) + sum_dec += Eigen::Vector3d(abs(dir.row(1)[0]), abs(dir.row(1)[1]), abs(dir.row(1)[2])) * data.value(); } - out.value() = sum ? (max / sum) : 0.0; } -}; + if ((sum_dec.array() != 0.0).any()) + sum_dec.normalize(); + for (out.index(3) = 0; out.index(3) != 3; ++out.index(3)) + out.value() = sum_dec[size_t(out.index(3))]; + } +protected: + FixelDataType vol; + Image dir; +}; -class DEC_unit : protected Base -{ - public: - DEC_unit (FixelDataType& data, const index_type max_fixels, FixelDataType& vol, Image& dir) : - Base (data, max_fixels), - vol (vol), dir (dir) {} - - void operator() (Image& index, Image& out) - { - Eigen::Vector3d sum_dec = {0.0, 0.0, 0.0}; - if (vol.valid()) { - for (auto f = Base::Loop (index) (data, vol, dir); f; ++f) { - if (!f.padding()) - sum_dec += Eigen::Vector3d (abs (dir.row(1)[0]), abs (dir.row(1)[1]), abs (dir.row(1)[2])) * data.value() * vol.value(); - } - } else { - for (auto f = Base::Loop (index) (data, dir); f; ++f) { - if (!f.padding()) - sum_dec += Eigen::Vector3d (abs (dir.row(1)[0]), abs (dir.row(1)[1]), abs (dir.row(1)[2])) * data.value(); +class DEC_scaled : protected Base { +public: + DEC_scaled(FixelDataType &data, const index_type max_fixels, FixelDataType &vol, Image &dir) + : Base(data, max_fixels), vol(vol), dir(dir) {} + + void operator()(FixelIndexType &index, Image &out) { + Eigen::Vector3d sum_dec = {0.0, 0.0, 0.0}; + default_type sum_value = 0.0; + if (vol.valid()) { + default_type sum_volume = 0.0; + for (auto f = Base::Loop(index)(data, vol, dir); f; ++f) { + if (!f.padding()) { + sum_dec += + Eigen::Vector3d(abs(dir.row(1)[0]), abs(dir.row(1)[1]), abs(dir.row(1)[2])) * data.value() * vol.value(); + sum_volume += vol.value(); + sum_value += vol.value() * data.value(); } } if ((sum_dec.array() != 0.0).any()) sum_dec.normalize(); - for (out.index(3) = 0; out.index(3) != 3; ++out.index(3)) - out.value() = sum_dec[size_t(out.index(3))]; - } - protected: - FixelDataType vol; - Image dir; -}; - - -class DEC_scaled : protected Base -{ - public: - DEC_scaled (FixelDataType& data, const index_type max_fixels, FixelDataType& vol, Image& dir) : - Base (data, max_fixels), - vol (vol), dir (dir) {} - - void operator() (FixelIndexType& index, Image& out) - { - Eigen::Vector3d sum_dec = {0.0, 0.0, 0.0}; - default_type sum_value = 0.0; - if (vol.valid()) { - default_type sum_volume = 0.0; - for (auto f = Base::Loop (index) (data, vol, dir); f; ++f) { - if (!f.padding()) { - sum_dec += Eigen::Vector3d (abs (dir.row(1)[0]), abs (dir.row(1)[1]), abs (dir.row(1)[2])) * data.value() * vol.value(); - sum_volume += vol.value(); - sum_value += vol.value() * data.value(); - } - } - if ((sum_dec.array() != 0.0).any()) - sum_dec.normalize(); - sum_dec *= (sum_value / sum_volume); - } else { - for (auto f = Base::Loop (index) (data, dir); f; ++f) { - if (!f.padding()) { - sum_dec += Eigen::Vector3d (abs (dir.row(1)[0]), abs (dir.row(1)[1]), abs (dir.row(1)[2])) * data.value(); - sum_value += data.value(); - } + sum_dec *= (sum_value / sum_volume); + } else { + for (auto f = Base::Loop(index)(data, dir); f; ++f) { + if (!f.padding()) { + sum_dec += Eigen::Vector3d(abs(dir.row(1)[0]), abs(dir.row(1)[1]), abs(dir.row(1)[2])) * data.value(); + sum_value += data.value(); } - if ((sum_dec.array() != 0.0).any()) - sum_dec.normalize(); - sum_dec *= sum_value; } - for (out.index(3) = 0; out.index(3) != 3; ++out.index(3)) - out.value() = sum_dec[size_t(out.index(3))]; + if ((sum_dec.array() != 0.0).any()) + sum_dec.normalize(); + sum_dec *= sum_value; } - protected: - FixelDataType vol; - Image dir; -}; + for (out.index(3) = 0; out.index(3) != 3; ++out.index(3)) + out.value() = sum_dec[size_t(out.index(3))]; + } +protected: + FixelDataType vol; + Image dir; +}; -class None : protected Base -{ - public: - None (FixelDataType& data, const index_type max_fixels, const float fill_value) : - Base (data, max_fixels, true, fill_value) { } +class None : protected Base { +public: + None(FixelDataType &data, const index_type max_fixels, const float fill_value) + : Base(data, max_fixels, true, fill_value) {} - void operator() (FixelIndexType& index, Image& out) - { - for (auto f = Base::Loop (index) (data); f; ++f) { - out.index(3) = f.fixel_index; - out.value() = f.padding() ? pad_value : data.value(); - } + void operator()(FixelIndexType &index, Image &out) { + for (auto f = Base::Loop(index)(data); f; ++f) { + out.index(3) = f.fixel_index; + out.value() = f.padding() ? pad_value : data.value(); } + } }; - - - - - - -void run () -{ - auto in_data = Fixel::open_fixel_data_file (argument[0]); +void run() { + auto in_data = Fixel::open_fixel_data_file(argument[0]); if (in_data.size(2) != 1) - throw Exception ("Input fixel data file must have a single scalar value per fixel (i.e. have dimensions Nx1x1)"); + throw Exception("Input fixel data file must have a single scalar value per fixel (i.e. have dimensions Nx1x1)"); - Header in_index_header = Fixel::find_index_header (Fixel::get_fixel_directory (argument[0])); + Header in_index_header = Fixel::find_index_header(Fixel::get_fixel_directory(argument[0])); auto in_index_image = in_index_header.get_image(); Image in_directions; const int op = argument[1]; - const index_type max_fixels = get_option_value ("number", 0); + const index_type max_fixels = get_option_value("number", 0); if (max_fixels && op == 7) - throw Exception ("\"count\" statistic is meaningless if constraining the number of fixels per voxel using the -number option"); + throw Exception( + "\"count\" statistic is meaningless if constraining the number of fixels per voxel using the -number option"); - Header H_out (in_index_header); + Header H_out(in_index_header); H_out.datatype() = DataType::Float32; H_out.datatype().set_byte_order_native(); - H_out.keyval().erase (Fixel::n_fixels_key); + H_out.keyval().erase(Fixel::n_fixels_key); if (op == 7) { // count H_out.ndim() = 3; H_out.datatype() = DataType::UInt8; } else if (op == 10 || op == 11) { // dec H_out.ndim() = 4; - H_out.size (3) = 3; + H_out.size(3) = 3; } else if (op == 12) { // none H_out.ndim() = 4; if (max_fixels) { H_out.size(3) = max_fixels; } else { index_type max_count = 0; - for (auto l = Loop ("determining largest fixel count", in_index_image, 0, 3) (in_index_image); l; ++l) - max_count = std::max (max_count, (index_type)in_index_image.value()); + for (auto l = Loop("determining largest fixel count", in_index_image, 0, 3)(in_index_image); l; ++l) + max_count = std::max(max_count, (index_type)in_index_image.value()); if (max_count == 0) - throw Exception ("fixel image is empty"); + throw Exception("fixel image is empty"); // 3 volumes per fixel if performing split_dir H_out.size(3) = max_count; } @@ -530,53 +468,80 @@ void run () H_out.ndim() = 3; } - if (op == 10 || op == 11) // dec - in_directions = Fixel::find_directions_header ( - Fixel::get_fixel_directory (in_data.name())).get_image().with_direct_io(); + if (op == 10 || op == 11) // dec + in_directions = + Fixel::find_directions_header(Fixel::get_fixel_directory(in_data.name())).get_image().with_direct_io(); FixelDataType in_vol; - auto opt = get_options ("weighted"); + auto opt = get_options("weighted"); if (opt.size()) { - in_vol = FixelDataType::open (opt[0][0]); - check_dimensions (in_data, in_vol); + in_vol = FixelDataType::open(opt[0][0]); + check_dimensions(in_data, in_vol); } - if (op == 2 || op == 3 || op == 4 || op == 5 || op == 6 || - op == 7 || op == 8 || op == 9 || op == 12) { + if (op == 2 || op == 3 || op == 4 || op == 5 || op == 6 || op == 7 || op == 8 || op == 9 || op == 12) { if (in_vol.valid()) - WARN ("Option -weighted has no meaningful interpretation for the operation specified; ignoring"); + WARN("Option -weighted has no meaningful interpretation for the operation specified; ignoring"); } - opt = get_options ("fill"); + opt = get_options("fill"); float fill_value = 0.0; if (opt.size()) { if (op == 12) { fill_value = opt[0][0]; } else { - WARN ("Option -fill ignored; only applicable to \"none\" operation"); + WARN("Option -fill ignored; only applicable to \"none\" operation"); } } - auto out = Image::create (argument[2], H_out); + auto out = Image::create(argument[2], H_out); - auto loop = ThreadedLoop ("converting sparse fixel data to scalar image", in_index_image, 0, 3); + auto loop = ThreadedLoop("converting sparse fixel data to scalar image", in_index_image, 0, 3); switch (op) { - case 0: loop.run (Mean (in_data, max_fixels, in_vol), in_index_image, out); break; - case 1: loop.run (Sum (in_data, max_fixels, in_vol), in_index_image, out); break; - case 2: loop.run (Product (in_data, max_fixels), in_index_image, out); break; - case 3: loop.run (Min (in_data, max_fixels), in_index_image, out); break; - case 4: loop.run (Max (in_data, max_fixels), in_index_image, out); break; - case 5: loop.run (AbsMax (in_data, max_fixels), in_index_image, out); break; - case 6: loop.run (MagMax (in_data, max_fixels), in_index_image, out); break; - case 7: loop.run ([](Image& index, Image& out) { // count - out.value() = index.value(); - }, in_index_image, out); break; - case 8: loop.run (Complexity (in_data, max_fixels), in_index_image, out); break; - case 9: loop.run (SF (in_data, max_fixels), in_index_image, out); break; - case 10: loop.run (DEC_unit (in_data, max_fixels, in_vol, in_directions), in_index_image, out); break; - case 11: loop.run (DEC_scaled (in_data, max_fixels, in_vol, in_directions), in_index_image, out); break; - case 12: loop.run (::None (in_data, max_fixels, fill_value), in_index_image, out); break; + case 0: + loop.run(Mean(in_data, max_fixels, in_vol), in_index_image, out); + break; + case 1: + loop.run(Sum(in_data, max_fixels, in_vol), in_index_image, out); + break; + case 2: + loop.run(Product(in_data, max_fixels), in_index_image, out); + break; + case 3: + loop.run(Min(in_data, max_fixels), in_index_image, out); + break; + case 4: + loop.run(Max(in_data, max_fixels), in_index_image, out); + break; + case 5: + loop.run(AbsMax(in_data, max_fixels), in_index_image, out); + break; + case 6: + loop.run(MagMax(in_data, max_fixels), in_index_image, out); + break; + case 7: + loop.run( + [](Image &index, Image &out) { // count + out.value() = index.value(); + }, + in_index_image, + out); + break; + case 8: + loop.run(Complexity(in_data, max_fixels), in_index_image, out); + break; + case 9: + loop.run(SF(in_data, max_fixels), in_index_image, out); + break; + case 10: + loop.run(DEC_unit(in_data, max_fixels, in_vol, in_directions), in_index_image, out); + break; + case 11: + loop.run(DEC_scaled(in_data, max_fixels, in_vol, in_directions), in_index_image, out); + break; + case 12: + loop.run(::None(in_data, max_fixels, fill_value), in_index_image, out); + break; } - } diff --git a/cmd/fixelcfestats.cpp b/cmd/fixelcfestats.cpp index cfdd6b0faf..bac8b278ba 100644 --- a/cmd/fixelcfestats.cpp +++ b/cmd/fixelcfestats.cpp @@ -14,33 +14,32 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "image.h" -#include "progressbar.h" -#include "thread_queue.h" -#include "transform.h" #include "algo/loop.h" +#include "command.h" #include "file/matrix.h" +#include "fixel/filter/smooth.h" #include "fixel/fixel.h" #include "fixel/helpers.h" #include "fixel/index_remapper.h" #include "fixel/loop.h" -#include "fixel/filter/smooth.h" +#include "image.h" #include "math/stats/fwe.h" #include "math/stats/glm.h" #include "math/stats/import.h" #include "math/stats/shuffle.h" #include "math/stats/typedefs.h" +#include "progressbar.h" #include "stats/cfe.h" #include "stats/enhance.h" #include "stats/permtest.h" - +#include "thread_queue.h" +#include "transform.h" using namespace MR; using namespace App; -//using Fixel::index_type; -//using Math::Stats::index_type; +// using Fixel::index_type; +// using Math::Stats::index_type; using Math::Stats::matrix_type; using Math::Stats::value_type; using Math::Stats::vector_type; @@ -56,192 +55,181 @@ using Stats::PermTest::count_matrix_type; #define DEFAULT_CFE_C 0.5 #define DEFAULT_EMPIRICAL_SKEW 1.0 // TODO Update from experience -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Fixel-based analysis using connectivity-based fixel enhancement and non-parametric permutation testing"; DESCRIPTION - + "Unlike previous versions of this command, where a whole-brain tractogram file would be provided as input " - "in order to generate the fixel-fixel connectivity matrix and smooth fixel data, this version expects to be " - "provided with the directory path to a pre-calculated fixel-fixel connectivity matrix (likely generated using " - "the MRtrix3 command fixelconnectivity), and for the input fixel data to have already been smoothed (likely " - "using the MRtrix3 command fixelfilter)." - - + "Note that if the -mask option is used, the output fixel directory will still contain the same set of fixels as that " - "present in the input fixel template, in order to retain fixel correspondence. However a consequence of this is that " - "all fixels in the template will be initialy visible when the output fixel directory is loaded in mrview. Those fixels " - "outside the processing mask will immediately disappear from view as soon as any data-file-based fixel colouring or " - "thresholding is applied." - - + Math::Stats::GLM::column_ones_description - - + Fixel::format_description; + +"Unlike previous versions of this command, where a whole-brain tractogram file would be provided as input " + "in order to generate the fixel-fixel connectivity matrix and smooth fixel data, this version expects to be " + "provided with the directory path to a pre-calculated fixel-fixel connectivity matrix (likely generated using " + "the MRtrix3 command fixelconnectivity), and for the input fixel data to have already been smoothed (likely " + "using the MRtrix3 command fixelfilter)." + + + "Note that if the -mask option is used, the output fixel directory will still contain the same set of fixels " + "as that " + "present in the input fixel template, in order to retain fixel correspondence. However a consequence of this " + "is that " + "all fixels in the template will be initialy visible when the output fixel directory is loaded in mrview. " + "Those fixels " + "outside the processing mask will immediately disappear from view as soon as any data-file-based fixel " + "colouring or " + "thresholding is applied." + + + Math::Stats::GLM::column_ones_description + + + Fixel::format_description; REFERENCES - + "Raffelt, D.; Smith, RE.; Ridgway, GR.; Tournier, JD.; Vaughan, DN.; Rose, S.; Henderson, R.; Connelly, A. " // Internal - "Connectivity-based fixel enhancement: Whole-brain statistical analysis of diffusion MRI measures in the presence of crossing fibres." - "Neuroimage, 2015, 15(117):40-55" + +"Raffelt, D.; Smith, RE.; Ridgway, GR.; Tournier, JD.; Vaughan, DN.; Rose, S.; Henderson, R.; Connelly, A. " // Internal + "Connectivity-based fixel enhancement: Whole-brain statistical analysis of diffusion MRI measures in the presence " + "of crossing fibres." + "Neuroimage, 2015, 15(117):40-55" - + "* If not using the -cfe_legacy option: \n" - "Smith, RE.; Dimond, D; Vaughan, D.; Parker, D.; Dhollander, T.; Jackson, G.; Connelly, A. " - "Intrinsic non-stationarity correction for Fixel-Based Analysis. " - "In Proc OHBM 2019 M789" + + "* If not using the -cfe_legacy option: \n" + "Smith, RE.; Dimond, D; Vaughan, D.; Parker, D.; Dhollander, T.; Jackson, G.; Connelly, A. " + "Intrinsic non-stationarity correction for Fixel-Based Analysis. " + "In Proc OHBM 2019 M789" - + "* If using the -nonstationary option: \n" - "Salimi-Khorshidi, G. Smith, S.M. Nichols, T.E. " - "Adjusting the effect of nonstationarity in cluster-based and TFCE inference. " - "NeuroImage, 2011, 54(3), 2006-19"; + + "* If using the -nonstationary option: \n" + "Salimi-Khorshidi, G. Smith, S.M. Nichols, T.E. " + "Adjusting the effect of nonstationarity in cluster-based and TFCE inference. " + "NeuroImage, 2011, 54(3), 2006-19"; ARGUMENTS - + Argument ("in_fixel_directory", "the fixel directory containing the data files for each subject (after obtaining fixel correspondence").type_directory_in() - - + Argument ("subjects", "a text file listing the subject identifiers (one per line). This should correspond with the filenames " - "in the fixel directory (including the file extension), and be listed in the same order as the rows of the design matrix.").type_image_in () + +Argument("in_fixel_directory", + "the fixel directory containing the data files for each subject (after obtaining fixel correspondence") + .type_directory_in() - + Argument ("design", "the design matrix").type_file_in () + + + Argument("subjects", + "a text file listing the subject identifiers (one per line). This should correspond with the filenames " + "in the fixel directory (including the file extension), and be listed in the same order as the rows of " + "the design matrix.") + .type_image_in() - + Argument ("contrast", "the contrast matrix, specified as rows of weights").type_file_in () + + Argument("design", "the design matrix").type_file_in() - // .type_various() rather than .type_directory_in() to catch people trying to - // pass a track file, and give a more informative error message - + Argument ("connectivity", "the fixel-fixel connectivity matrix").type_various () + + Argument("contrast", "the contrast matrix, specified as rows of weights").type_file_in() - + Argument ("out_fixel_directory", "the output directory where results will be saved. Will be created if it does not exist").type_text(); + // .type_various() rather than .type_directory_in() to catch people trying to + // pass a track file, and give a more informative error message + + Argument("connectivity", "the fixel-fixel connectivity matrix").type_various() + + Argument("out_fixel_directory", + "the output directory where results will be saved. Will be created if it does not exist") + .type_text(); OPTIONS - + Option ("mask", "provide a fixel data file containing a mask of those fixels to be used during processing") - + Argument ("file").type_image_in() + +Option("mask", "provide a fixel data file containing a mask of those fixels to be used during processing") + + Argument("file").type_image_in() - + Math::Stats::shuffle_options (true, DEFAULT_EMPIRICAL_SKEW) + + Math::Stats::shuffle_options(true, DEFAULT_EMPIRICAL_SKEW) - + OptionGroup ("Parameters for the Connectivity-based Fixel Enhancement algorithm") + + OptionGroup("Parameters for the Connectivity-based Fixel Enhancement algorithm") - + Option ("cfe_dh", "the height increment used in the cfe integration (default: " + str(DEFAULT_CFE_DH, 2) + ")") - + Argument ("value").type_float (0.001, 1.0) + + Option("cfe_dh", "the height increment used in the cfe integration (default: " + str(DEFAULT_CFE_DH, 2) + ")") + + Argument("value").type_float(0.001, 1.0) - + Option ("cfe_e", "cfe extent exponent (default: " + str(DEFAULT_CFE_E, 2) + ")") - + Argument ("value").type_float (0.0, 100.0) + + Option("cfe_e", "cfe extent exponent (default: " + str(DEFAULT_CFE_E, 2) + ")") + + Argument("value").type_float(0.0, 100.0) - + Option ("cfe_h", "cfe height exponent (default: " + str(DEFAULT_CFE_H, 2) + ")") - + Argument ("value").type_float (0.0, 100.0) + + Option("cfe_h", "cfe height exponent (default: " + str(DEFAULT_CFE_H, 2) + ")") + + Argument("value").type_float(0.0, 100.0) - + Option ("cfe_c", "cfe connectivity exponent (default: " + str(DEFAULT_CFE_C, 2) + ")") - + Argument ("value").type_float (0.0, 100.0) + + Option("cfe_c", "cfe connectivity exponent (default: " + str(DEFAULT_CFE_C, 2) + ")") + + Argument("value").type_float(0.0, 100.0) - + Option ("cfe_legacy", "use the legacy (non-normalised) form of the cfe equation") - - + Math::Stats::GLM::glm_options ("fixel"); + + Option("cfe_legacy", "use the legacy (non-normalised) form of the cfe equation") + + Math::Stats::GLM::glm_options("fixel"); } - - template -void write_fixel_output (const std::string& filename, - const VectorType& data, - Image& mask, - const Header& header) -{ - auto output = Image::create (filename, header); - for (auto l = Loop(0) (output, mask); l; ++l) +void write_fixel_output(const std::string &filename, const VectorType &data, Image &mask, const Header &header) { + auto output = Image::create(filename, header); + for (auto l = Loop(0)(output, mask); l; ++l) output.value() = mask.value() ? data[output.index(0)] : NaN; } - - // Define data importer class that will obtain fixel data for a // specific subject based on the string path to the image file for // that subject -class SubjectFixelImport : public Math::Stats::SubjectDataImportBase -{ - public: - SubjectFixelImport (const std::string& path) : - Math::Stats::SubjectDataImportBase (path), - H (Header::open (path)), - data (H.get_image()) - { - for (size_t axis = 1; axis < data.ndim(); ++axis) { - if (data.size(axis) > 1) - throw Exception ("Image file \"" + path + "\" does not contain fixel data (wrong dimensions)"); - } - } - - void operator() (matrix_type::RowXpr row) const override - { - Image temp (data); // For thread-safety - for (temp.index(0) = 0; temp.index(0) != temp.size(0); ++temp.index(0)) - row [temp.index(0)] = temp.value(); +class SubjectFixelImport : public Math::Stats::SubjectDataImportBase { +public: + SubjectFixelImport(const std::string &path) + : Math::Stats::SubjectDataImportBase(path), H(Header::open(path)), data(H.get_image()) { + for (size_t axis = 1; axis < data.ndim(); ++axis) { + if (data.size(axis) > 1) + throw Exception("Image file \"" + path + "\" does not contain fixel data (wrong dimensions)"); } + } - default_type operator[] (const Math::Stats::index_type index) const override - { - Image temp (data); // For thread-safety - temp.index(0) = index; - assert (!is_out_of_bounds (temp)); - return default_type(temp.value()); - } - - Math::Stats::index_type size() const override { return data.size(0); } - - const Header& header() const { return H; } + void operator()(matrix_type::RowXpr row) const override { + Image temp(data); // For thread-safety + for (temp.index(0) = 0; temp.index(0) != temp.size(0); ++temp.index(0)) + row[temp.index(0)] = temp.value(); + } + default_type operator[](const Math::Stats::index_type index) const override { + Image temp(data); // For thread-safety + temp.index(0) = index; + assert(!is_out_of_bounds(temp)); + return default_type(temp.value()); + } + Math::Stats::index_type size() const override { return data.size(0); } - private: - Header H; - Image data; // May be mapped input file, or scratch smoothed data + const Header &header() const { return H; } +private: + Header H; + Image data; // May be mapped input file, or scratch smoothed data }; +void run() { + if (Path::has_suffix(argument[4], ".tck")) + throw Exception("This version of fixelcfestats requires as input not a track file, but a " + "pre-calculated fixel-fixel connectivity matrix; in addition, input fixel " + "data must be pre-smoothed. Please check command / pipeline documentation " + "specific to this software version."); + const value_type cfe_dh = get_option_value("cfe_dh", DEFAULT_CFE_DH); + const value_type cfe_h = get_option_value("cfe_h", DEFAULT_CFE_H); + const value_type cfe_e = get_option_value("cfe_e", DEFAULT_CFE_E); + const value_type cfe_c = get_option_value("cfe_c", DEFAULT_CFE_C); + const bool cfe_legacy = get_options("cfe_legacy").size(); - -void run() -{ - if (Path::has_suffix (argument[4], ".tck")) - throw Exception ("This version of fixelcfestats requires as input not a track file, but a " - "pre-calculated fixel-fixel connectivity matrix; in addition, input fixel " - "data must be pre-smoothed. Please check command / pipeline documentation " - "specific to this software version."); - - const value_type cfe_dh = get_option_value ("cfe_dh", DEFAULT_CFE_DH); - const value_type cfe_h = get_option_value ("cfe_h", DEFAULT_CFE_H); - const value_type cfe_e = get_option_value ("cfe_e", DEFAULT_CFE_E); - const value_type cfe_c = get_option_value ("cfe_c", DEFAULT_CFE_C); - const bool cfe_legacy = get_options ("cfe_legacy").size(); - - const bool do_nonstationarity_adjustment = get_options ("nonstationarity").size(); - const default_type empirical_skew = get_option_value ("skew_nonstationarity", DEFAULT_EMPIRICAL_SKEW); + const bool do_nonstationarity_adjustment = get_options("nonstationarity").size(); + const default_type empirical_skew = get_option_value("skew_nonstationarity", DEFAULT_EMPIRICAL_SKEW); const std::string input_fixel_directory = argument[0]; - Header index_header = Fixel::find_index_header (input_fixel_directory); + Header index_header = Fixel::find_index_header(input_fixel_directory); auto index_image = index_header.get_image(); - const Fixel::index_type num_fixels = Fixel::get_number_of_fixels (index_header); - CONSOLE ("Number of fixels in template: " + str(num_fixels)); + const Fixel::index_type num_fixels = Fixel::get_number_of_fixels(index_header); + CONSOLE("Number of fixels in template: " + str(num_fixels)); Image mask; - auto opt = get_options ("mask"); + auto opt = get_options("mask"); Fixel::index_type mask_fixels = 0; if (opt.size()) { - mask = Image::open (opt[0][0]); - Fixel::check_data_file (mask); - if (!Fixel::fixels_match (index_header, mask)) - throw Exception ("Mask image provided using -mask option does not match fixel template"); - for (auto l = Loop(0) (mask); l; ++l) { + mask = Image::open(opt[0][0]); + Fixel::check_data_file(mask); + if (!Fixel::fixels_match(index_header, mask)) + throw Exception("Mask image provided using -mask option does not match fixel template"); + for (auto l = Loop(0)(mask); l; ++l) { if (mask.value()) ++mask_fixels; } - CONSOLE ("Number of fixels in mask: " + str(mask_fixels)); + CONSOLE("Number of fixels in mask: " + str(mask_fixels)); } else { - Header fixel_mask_header = Fixel::data_header_from_index (index_header); + Header fixel_mask_header = Fixel::data_header_from_index(index_header); fixel_mask_header.datatype() = DataType::Bit; - mask = Image::scratch (fixel_mask_header, "true-filled scratch fixel mask"); - for (auto l = Loop(0) (mask); l; ++l) + mask = Image::scratch(fixel_mask_header, "true-filled scratch fixel mask"); + for (auto l = Loop(0)(mask); l; ++l) mask.value() = true; mask_fixels = num_fixels; } @@ -249,37 +237,37 @@ void run() // Read file names and check files exist // Preference for finding files relative to input template fixel directory Math::Stats::CohortDataImport importer; - importer.initialise (argument[1], input_fixel_directory); + importer.initialise(argument[1], input_fixel_directory); for (Math::Stats::index_type i = 0; i != importer.size(); ++i) { - if (!Fixel::fixels_match (index_header, dynamic_cast(importer[i].get())->header())) - throw Exception ("Fixel data file \"" + importer[i]->name() + "\" does not match template fixel image"); + if (!Fixel::fixels_match(index_header, dynamic_cast(importer[i].get())->header())) + throw Exception("Fixel data file \"" + importer[i]->name() + "\" does not match template fixel image"); } - CONSOLE ("Number of inputs: " + str(importer.size())); + CONSOLE("Number of inputs: " + str(importer.size())); // Load design matrix: - const matrix_type design = File::Matrix::load_matrix (argument[2]); + const matrix_type design = File::Matrix::load_matrix(argument[2]); if (size_t(design.rows()) != importer.size()) - throw Exception ("Number of input files does not match number of rows in design matrix"); + throw Exception("Number of input files does not match number of rows in design matrix"); // Before validating the contrast matrix, we first need to see if there are any // additional design matrix columns coming from fixel-wise subject data vector extra_columns; bool nans_in_columns = false; - opt = get_options ("column"); + opt = get_options("column"); for (size_t i = 0; i != opt.size(); ++i) { - extra_columns.push_back (Math::Stats::CohortDataImport()); - extra_columns[i].initialise (opt[i][0]); + extra_columns.push_back(Math::Stats::CohortDataImport()); + extra_columns[i].initialise(opt[i][0]); // Check for non-finite values in mask fixels only // Can't use generic allFinite() function; need to populate matrix data if (!nans_in_columns) { - matrix_type column_data (importer.size(), num_fixels); + matrix_type column_data(importer.size(), num_fixels); for (Math::Stats::index_type j = 0; j != importer.size(); ++j) - (*extra_columns[i][j]) (column_data.row (j)); + (*extra_columns[i][j])(column_data.row(j)); if (mask_fixels == num_fixels) { nans_in_columns = !column_data.allFinite(); } else { - for (auto l = Loop(0) (mask); l; ++l) { - if (mask.value() && !column_data.col (mask.index(0)).allFinite()) { + for (auto l = Loop(0)(mask); l; ++l) { + if (mask.value() && !column_data.col(mask.index(0)).allFinite()) { nans_in_columns = true; break; } @@ -288,34 +276,36 @@ void run() } } const Math::Stats::index_type num_factors = design.cols() + extra_columns.size(); - CONSOLE ("Number of factors: " + str(num_factors)); + CONSOLE("Number of factors: " + str(num_factors)); if (extra_columns.size()) { - CONSOLE ("Number of element-wise design matrix columns: " + str(extra_columns.size())); + CONSOLE("Number of element-wise design matrix columns: " + str(extra_columns.size())); if (nans_in_columns) - CONSOLE ("Non-finite values detected in element-wise design matrix columns; individual rows will be removed from fixel-wise design matrices accordingly"); + CONSOLE("Non-finite values detected in element-wise design matrix columns; individual rows will be removed from " + "fixel-wise design matrices accordingly"); } - Math::Stats::GLM::check_design (design, extra_columns.size()); + Math::Stats::GLM::check_design(design, extra_columns.size()); // Load variance groups - auto variance_groups = Math::Stats::GLM::load_variance_groups (design.rows()); - const Math::Stats::index_type num_vgs = variance_groups.size() ? variance_groups.maxCoeff()+1 : 1; + auto variance_groups = Math::Stats::GLM::load_variance_groups(design.rows()); + const Math::Stats::index_type num_vgs = variance_groups.size() ? variance_groups.maxCoeff() + 1 : 1; if (num_vgs > 1) - CONSOLE ("Number of variance groups: " + str(num_vgs)); + CONSOLE("Number of variance groups: " + str(num_vgs)); // Load hypotheses - const vector hypotheses = Math::Stats::GLM::load_hypotheses (argument[3]); + const vector hypotheses = Math::Stats::GLM::load_hypotheses(argument[3]); const Math::Stats::index_type num_hypotheses = hypotheses.size(); if (hypotheses[0].cols() != num_factors) - throw Exception ("The number of columns in the contrast matrix (" + str(hypotheses[0].cols()) + ")" - + (extra_columns.size() ? " (in addition to the " + str(extra_columns.size()) + " uses of -column)" : "") - + " does not equal the number of columns in the design matrix (" + str(design.cols()) + ")"); - CONSOLE ("Number of hypotheses: " + str(num_hypotheses)); + throw Exception( + "The number of columns in the contrast matrix (" + str(hypotheses[0].cols()) + ")" + + (extra_columns.size() ? " (in addition to the " + str(extra_columns.size()) + " uses of -column)" : "") + + " does not equal the number of columns in the design matrix (" + str(design.cols()) + ")"); + CONSOLE("Number of hypotheses: " + str(num_hypotheses)); // Load fixel-fixel connectivity matrix - Fixel::Matrix::Reader matrix (argument[4], mask); + Fixel::Matrix::Reader matrix(argument[4], mask); const std::string output_fixel_directory = argument[5]; - Fixel::copy_index_and_directions_file (input_fixel_directory, output_fixel_directory); + Fixel::copy_index_and_directions_file(input_fixel_directory, output_fixel_directory); // Do we still want to check whether or not there are any disconnected fixels? // With the current derivation, disconnected fixels will not possess any self-connectivity, @@ -327,89 +317,100 @@ void run() // zeroed by the enhancement process Fixel::index_type num_unconnected_fixels = 0; for (Fixel::index_type f = 0; f != num_fixels; ++f) { - mask.index (0) = f; - if (mask.value() && !matrix.size (f)) + mask.index(0) = f; + if (mask.value() && !matrix.size(f)) ++num_unconnected_fixels; } if (num_unconnected_fixels) { - WARN ("A total of " + str(num_unconnected_fixels) + " fixels " + - (mask_fixels == num_fixels ? "" : "in the provided mask ") + - "do not possess any streamlines-based connectivity; " - "these will not be enhanced by CFE, and hence cannot be " - "tested for statistical significance"); + WARN("A total of " + str(num_unconnected_fixels) + " fixels " + + (mask_fixels == num_fixels ? "" : "in the provided mask ") + + "do not possess any streamlines-based connectivity; " + "these will not be enhanced by CFE, and hence cannot be " + "tested for statistical significance"); } - Header output_header (dynamic_cast(importer[0].get())->header()); + Header output_header(dynamic_cast(importer[0].get())->header()); output_header.keyval()["cfe_dh"] = str(cfe_dh); output_header.keyval()["cfe_e"] = str(cfe_e); output_header.keyval()["cfe_h"] = str(cfe_h); output_header.keyval()["cfe_c"] = str(cfe_c); output_header.keyval()["cfe_legacy"] = str(cfe_legacy); - matrix_type data = matrix_type::Zero (importer.size(), num_fixels); + matrix_type data = matrix_type::Zero(importer.size(), num_fixels); { - ProgressBar progress (std::string ("Loading fixel data (no smoothing)"), importer.size()); + ProgressBar progress(std::string("Loading fixel data (no smoothing)"), importer.size()); for (Math::Stats::index_type subject = 0; subject != importer.size(); subject++) { - (*importer[subject]) (data.row (subject)); + (*importer[subject])(data.row(subject)); progress++; } } // Detect non-finite values in mask fixels only; NaN-fill other fixels bool nans_in_data = false; - for (auto l = Loop(0) (mask); l; ++l) { + for (auto l = Loop(0)(mask); l; ++l) { if (mask.value()) { - if (!data.col (mask.index(0)).allFinite()) + if (!data.col(mask.index(0)).allFinite()) nans_in_data = true; } else { - data.col (mask.index (0)).fill (NaN); + data.col(mask.index(0)).fill(NaN); } } if (nans_in_data) { - CONSOLE ("Non-finite values present in data; rows will be removed from fixel-wise design matrices accordingly"); + CONSOLE("Non-finite values present in data; rows will be removed from fixel-wise design matrices accordingly"); if (!extra_columns.size()) { - CONSOLE ("(Note that this will result in slower execution than if such values were not present)"); + CONSOLE("(Note that this will result in slower execution than if such values were not present)"); } } // Only add contrast matrix row number to image outputs if there's more than one hypothesis - auto postfix = [&] (const Math::Stats::index_type i) -> std::string { return (num_hypotheses > 1) ? ("_" + hypotheses[i].name()) : ""; }; + auto postfix = [&](const Math::Stats::index_type i) -> std::string { + return (num_hypotheses > 1) ? ("_" + hypotheses[i].name()) : ""; + }; { - matrix_type betas (num_factors, num_fixels); - matrix_type abs_effect_size (num_fixels, num_hypotheses); - matrix_type std_effect_size (num_fixels, num_hypotheses); - matrix_type stdev (num_vgs, num_fixels); - vector_type cond (num_fixels); + matrix_type betas(num_factors, num_fixels); + matrix_type abs_effect_size(num_fixels, num_hypotheses); + matrix_type std_effect_size(num_fixels, num_hypotheses); + matrix_type stdev(num_vgs, num_fixels); + vector_type cond(num_fixels); - Math::Stats::GLM::all_stats (data, design, extra_columns, hypotheses, variance_groups, - cond, betas, abs_effect_size, std_effect_size, stdev); + Math::Stats::GLM::all_stats( + data, design, extra_columns, hypotheses, variance_groups, cond, betas, abs_effect_size, std_effect_size, stdev); - ProgressBar progress ("Outputting beta coefficients, effect size and standard deviation", num_factors + (2 * num_hypotheses) + num_vgs + (nans_in_data || extra_columns.size() ? 1 : 0)); + ProgressBar progress("Outputting beta coefficients, effect size and standard deviation", + num_factors + (2 * num_hypotheses) + num_vgs + (nans_in_data || extra_columns.size() ? 1 : 0)); for (Math::Stats::index_type i = 0; i != num_factors; ++i) { - write_fixel_output (Path::join (output_fixel_directory, "beta" + str(i) + ".mif"), betas.row(i), mask, output_header); + write_fixel_output( + Path::join(output_fixel_directory, "beta" + str(i) + ".mif"), betas.row(i), mask, output_header); ++progress; } for (Math::Stats::index_type i = 0; i != num_hypotheses; ++i) { if (!hypotheses[i].is_F()) { - write_fixel_output (Path::join (output_fixel_directory, "abs_effect" + postfix(i) + ".mif"), abs_effect_size.col(i), mask, output_header); + write_fixel_output(Path::join(output_fixel_directory, "abs_effect" + postfix(i) + ".mif"), + abs_effect_size.col(i), + mask, + output_header); ++progress; if (num_vgs == 1) - write_fixel_output (Path::join (output_fixel_directory, "std_effect" + postfix(i) + ".mif"), std_effect_size.col(i), mask, output_header); + write_fixel_output(Path::join(output_fixel_directory, "std_effect" + postfix(i) + ".mif"), + std_effect_size.col(i), + mask, + output_header); } else { ++progress; } ++progress; } if (nans_in_data || extra_columns.size()) { - write_fixel_output (Path::join (output_fixel_directory, "cond.mif"), cond, mask, output_header); + write_fixel_output(Path::join(output_fixel_directory, "cond.mif"), cond, mask, output_header); ++progress; } if (num_vgs == 1) { - write_fixel_output (Path::join (output_fixel_directory, "std_dev.mif"), stdev.row (0), mask, output_header); + write_fixel_output(Path::join(output_fixel_directory, "std_dev.mif"), stdev.row(0), mask, output_header); } else { for (Math::Stats::index_type i = 0; i != num_vgs; ++i) { - write_fixel_output (Path::join (output_fixel_directory, "std_dev" + str(i) + ".mif"), stdev.row (i), mask, output_header); + write_fixel_output( + Path::join(output_fixel_directory, "std_dev" + str(i) + ".mif"), stdev.row(i), mask, output_header); ++progress; } } @@ -419,41 +420,54 @@ void run() std::shared_ptr glm_test; if (extra_columns.size() || nans_in_data) { if (variance_groups.size()) - glm_test.reset (new Math::Stats::GLM::TestVariableHeteroscedastic (extra_columns, data, design, hypotheses, variance_groups, nans_in_data, nans_in_columns)); + glm_test.reset(new Math::Stats::GLM::TestVariableHeteroscedastic( + extra_columns, data, design, hypotheses, variance_groups, nans_in_data, nans_in_columns)); else - glm_test.reset (new Math::Stats::GLM::TestVariableHomoscedastic (extra_columns, data, design, hypotheses, nans_in_data, nans_in_columns)); + glm_test.reset(new Math::Stats::GLM::TestVariableHomoscedastic( + extra_columns, data, design, hypotheses, nans_in_data, nans_in_columns)); } else { if (variance_groups.size()) - glm_test.reset (new Math::Stats::GLM::TestFixedHeteroscedastic (data, design, hypotheses, variance_groups)); + glm_test.reset(new Math::Stats::GLM::TestFixedHeteroscedastic(data, design, hypotheses, variance_groups)); else - glm_test.reset (new Math::Stats::GLM::TestFixedHomoscedastic (data, design, hypotheses)); + glm_test.reset(new Math::Stats::GLM::TestFixedHomoscedastic(data, design, hypotheses)); } // Construct the class for performing fixel-based statistical enhancement - std::shared_ptr cfe_integrator (new Stats::CFE (matrix, cfe_dh, cfe_e, cfe_h, cfe_c, !cfe_legacy)); + std::shared_ptr cfe_integrator(new Stats::CFE(matrix, cfe_dh, cfe_e, cfe_h, cfe_c, !cfe_legacy)); // If performing non-stationarity adjustment we need to pre-compute the empirical CFE statistic matrix_type empirical_cfe_statistic; if (do_nonstationarity_adjustment) { - Stats::PermTest::precompute_empirical_stat (glm_test, cfe_integrator, empirical_skew, empirical_cfe_statistic); + Stats::PermTest::precompute_empirical_stat(glm_test, cfe_integrator, empirical_skew, empirical_cfe_statistic); output_header.keyval()["nonstationarity_adjustment"] = str(true); for (Math::Stats::index_type i = 0; i != num_hypotheses; ++i) - write_fixel_output (Path::join (output_fixel_directory, "cfe_empirical" + postfix(i) + ".mif"), empirical_cfe_statistic.col(i), mask, output_header); + write_fixel_output(Path::join(output_fixel_directory, "cfe_empirical" + postfix(i) + ".mif"), + empirical_cfe_statistic.col(i), + mask, + output_header); } else { output_header.keyval()["nonstationarity_adjustment"] = str(false); } // Precompute default statistic and CFE statistic matrix_type default_statistic, default_zstat, default_enhanced; - Stats::PermTest::precompute_default_permutation (glm_test, cfe_integrator, empirical_cfe_statistic, default_statistic, default_zstat, default_enhanced); + Stats::PermTest::precompute_default_permutation( + glm_test, cfe_integrator, empirical_cfe_statistic, default_statistic, default_zstat, default_enhanced); for (Math::Stats::index_type i = 0; i != num_hypotheses; ++i) { - write_fixel_output (Path::join (output_fixel_directory, (hypotheses[i].is_F() ? std::string("F") : std::string("t")) + "value" + postfix(i) + ".mif"), default_statistic.col(i), mask, output_header); - write_fixel_output (Path::join (output_fixel_directory, "Zstat" + postfix(i) + ".mif"), default_zstat.col(i), mask, output_header); - write_fixel_output (Path::join (output_fixel_directory, "cfe" + postfix(i) + ".mif"), default_enhanced.col(i), mask, output_header); + write_fixel_output( + Path::join(output_fixel_directory, + (hypotheses[i].is_F() ? std::string("F") : std::string("t")) + "value" + postfix(i) + ".mif"), + default_statistic.col(i), + mask, + output_header); + write_fixel_output( + Path::join(output_fixel_directory, "Zstat" + postfix(i) + ".mif"), default_zstat.col(i), mask, output_header); + write_fixel_output( + Path::join(output_fixel_directory, "cfe" + postfix(i) + ".mif"), default_enhanced.col(i), mask, output_header); } // Perform permutation testing - if (!get_options ("notest").size()) { + if (!get_options("notest").size()) { const bool fwe_strong = get_options("strong").size(); if (fwe_strong && num_hypotheses == 1) { @@ -462,29 +476,45 @@ void run() matrix_type null_distribution, uncorrected_pvalues; count_matrix_type null_contributions; - Stats::PermTest::run_permutations (glm_test, cfe_integrator, empirical_cfe_statistic, default_enhanced, fwe_strong, - null_distribution, null_contributions, uncorrected_pvalues); + Stats::PermTest::run_permutations(glm_test, + cfe_integrator, + empirical_cfe_statistic, + default_enhanced, + fwe_strong, + null_distribution, + null_contributions, + uncorrected_pvalues); - ProgressBar progress ("Outputting final results", (fwe_strong ? 1 : num_hypotheses) + 1 + 3*num_hypotheses); + ProgressBar progress("Outputting final results", (fwe_strong ? 1 : num_hypotheses) + 1 + 3 * num_hypotheses); if (fwe_strong) { - File::Matrix::save_vector (null_distribution.col(0), Path::join (output_fixel_directory, "null_dist.txt")); + File::Matrix::save_vector(null_distribution.col(0), Path::join(output_fixel_directory, "null_dist.txt")); ++progress; } else { for (Math::Stats::index_type i = 0; i != num_hypotheses; ++i) { - File::Matrix::save_vector (null_distribution.col(i), Path::join (output_fixel_directory, "null_dist" + postfix(i) + ".txt")); + File::Matrix::save_vector(null_distribution.col(i), + Path::join(output_fixel_directory, "null_dist" + postfix(i) + ".txt")); ++progress; } } - const matrix_type pvalue_output = MR::Math::Stats::fwe_pvalue (null_distribution, default_enhanced); + const matrix_type pvalue_output = MR::Math::Stats::fwe_pvalue(null_distribution, default_enhanced); ++progress; for (Math::Stats::index_type i = 0; i != num_hypotheses; ++i) { - write_fixel_output (Path::join (output_fixel_directory, "fwe_1mpvalue" + postfix(i) + ".mif"), pvalue_output.col(i), mask, output_header); + write_fixel_output(Path::join(output_fixel_directory, "fwe_1mpvalue" + postfix(i) + ".mif"), + pvalue_output.col(i), + mask, + output_header); ++progress; - write_fixel_output (Path::join (output_fixel_directory, "uncorrected_1mpvalue" + postfix(i) + ".mif"), uncorrected_pvalues.col(i), mask, output_header); + write_fixel_output(Path::join(output_fixel_directory, "uncorrected_1mpvalue" + postfix(i) + ".mif"), + uncorrected_pvalues.col(i), + mask, + output_header); ++progress; - write_fixel_output (Path::join (output_fixel_directory, "null_contributions" + postfix(i) + ".mif"), null_contributions.col(i), mask, output_header); + write_fixel_output(Path::join(output_fixel_directory, "null_contributions" + postfix(i) + ".mif"), + null_contributions.col(i), + mask, + output_header); ++progress; } } diff --git a/cmd/fixelconnectivity.cpp b/cmd/fixelconnectivity.cpp index 54acf987d4..a2ceea94b4 100644 --- a/cmd/fixelconnectivity.cpp +++ b/cmd/fixelconnectivity.cpp @@ -14,7 +14,6 @@ * For more details, see http://www.mrtrix.org/. */ - #include "command.h" #include "fixel/fixel.h" #include "fixel/helpers.h" @@ -22,129 +21,118 @@ #include "dwi/tractography/weights.h" #include "fixel/matrix.h" - - #define DEFAULT_ANGLE_THRESHOLD 45.0 #define DEFAULT_CONNECTIVITY_THRESHOLD 0.01 - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Generate a fixel-fixel connectivity matrix"; DESCRIPTION - + "This command will generate a directory containing three images, which encodes the " - "fixel-fixel connectivity matrix. Documentation regarding this format and how to " - "use it will come in the future." + +"This command will generate a directory containing three images, which encodes the " + "fixel-fixel connectivity matrix. Documentation regarding this format and how to " + "use it will come in the future." - + Fixel::format_description; + + Fixel::format_description; ARGUMENTS - + Argument ("fixel_directory", "the directory containing the fixels between which connectivity will be quantified").type_directory_in() + +Argument("fixel_directory", "the directory containing the fixels between which connectivity will be quantified") + .type_directory_in() - + Argument ("tracks", "the tracks used to determine fixel-fixel connectivity").type_tracks_in() - - + Argument ("matrix", "the output fixel-fixel connectivity matrix directory path").type_directory_out(); + + Argument("tracks", "the tracks used to determine fixel-fixel connectivity").type_tracks_in() + + Argument("matrix", "the output fixel-fixel connectivity matrix directory path").type_directory_out(); OPTIONS - + OptionGroup ("Options that influence generation of the connectivity matrix / matrices") - - + Option ("threshold", "a threshold to define the required fraction of shared connections to be included in the neighbourhood (default: " + str(DEFAULT_CONNECTIVITY_THRESHOLD, 2) + ")") - + Argument ("value").type_float (0.0, 1.0) + +OptionGroup("Options that influence generation of the connectivity matrix / matrices") - + Option ("angle", "the max angle threshold for assigning streamline tangents to fixels (Default: " + str(DEFAULT_ANGLE_THRESHOLD, 2) + " degrees)") - + Argument ("value").type_float (0.0, 90.0) + + Option("threshold", + "a threshold to define the required fraction of shared connections to be included in the neighbourhood " + "(default: " + + str(DEFAULT_CONNECTIVITY_THRESHOLD, 2) + ")") + + Argument("value").type_float(0.0, 1.0) - + Option ("mask", "provide a fixel data file containing a mask of those fixels to be computed; fixels outside the mask will be empty in the output matrix") - + Argument ("file").type_image_in() + + Option("angle", + "the max angle threshold for assigning streamline tangents to fixels (Default: " + + str(DEFAULT_ANGLE_THRESHOLD, 2) + " degrees)") + + Argument("value").type_float(0.0, 90.0) - + DWI::Tractography::TrackWeightsInOption + + Option("mask", + "provide a fixel data file containing a mask of those fixels to be computed; fixels outside the mask " + "will be empty in the output matrix") + + Argument("file").type_image_in() - + OptionGroup ("Options for additional outputs to be generated") + + DWI::Tractography::TrackWeightsInOption - + Option ("count", "export a fixel data file encoding the number of connections for each fixel") - + Argument ("path").type_image_out() + + OptionGroup("Options for additional outputs to be generated") - + Option ("extent", "export a fixel data file encoding the extent of connectivity (sum of weights) for each fixel") - + Argument ("path").type_image_out(); + + Option("count", "export a fixel data file encoding the number of connections for each fixel") + + Argument("path").type_image_out() + + + Option("extent", "export a fixel data file encoding the extent of connectivity (sum of weights) for each fixel") + + Argument("path").type_image_out(); } - using value_type = float; using Fixel::index_type; - - -template -void set_optional_outputs (WriterType& writer) -{ - auto opt = get_options ("count"); +template void set_optional_outputs(WriterType &writer) { + auto opt = get_options("count"); if (opt.size()) - writer.set_count_path (opt[0][0]); - opt = get_options ("extent"); + writer.set_count_path(opt[0][0]); + opt = get_options("extent"); if (opt.size()) - writer.set_extent_path (opt[0][0]); + writer.set_extent_path(opt[0][0]); } - - -void run() -{ - const value_type connectivity_threshold = get_option_value ("connectivity", value_type(DEFAULT_CONNECTIVITY_THRESHOLD)); - const value_type angular_threshold = get_option_value ("angle", value_type(DEFAULT_ANGLE_THRESHOLD)); +void run() { + const value_type connectivity_threshold = + get_option_value("connectivity", value_type(DEFAULT_CONNECTIVITY_THRESHOLD)); + const value_type angular_threshold = get_option_value("angle", value_type(DEFAULT_ANGLE_THRESHOLD)); const std::string input_fixel_directory = argument[0]; - Header index_header = Fixel::find_index_header (input_fixel_directory); + Header index_header = Fixel::find_index_header(input_fixel_directory); auto index_image = index_header.get_image(); - const index_type num_fixels = Fixel::get_number_of_fixels (index_image); + const index_type num_fixels = Fixel::get_number_of_fixels(index_image); // When provided with a mask, this only influences which fixels get their connectivity quantified; // these will appear empty in the output matrix - auto opt = get_options ("mask"); + auto opt = get_options("mask"); Image fixel_mask; if (opt.size()) { - fixel_mask = Image::open (opt[0][0]); - Fixel::check_data_file (fixel_mask); - if (!Fixel::fixels_match (index_header, fixel_mask)) - throw Exception ("Mask image provided using -mask option does not match input fixel directory"); + fixel_mask = Image::open(opt[0][0]); + Fixel::check_data_file(fixel_mask); + if (!Fixel::fixels_match(index_header, fixel_mask)) + throw Exception("Mask image provided using -mask option does not match input fixel directory"); } else { - Header mask_header = Fixel::data_header_from_index (index_header); + Header mask_header = Fixel::data_header_from_index(index_header); mask_header.datatype() = DataType::Bit; - fixel_mask = Image::scratch (mask_header, "true-filled scratch fixel mask"); + fixel_mask = Image::scratch(mask_header, "true-filled scratch fixel mask"); for (fixel_mask.index(0) = 0; fixel_mask.index(0) != num_fixels; ++fixel_mask.index(0)) fixel_mask.value() = true; } - if (get_options ("tck_weights_in").size()) { + if (get_options("tck_weights_in").size()) { - auto connectivity_matrix = Fixel::Matrix::generate_weighted (argument[1], - index_image, - fixel_mask, - angular_threshold); + auto connectivity_matrix = + Fixel::Matrix::generate_weighted(argument[1], index_image, fixel_mask, angular_threshold); - Fixel::Matrix::Writer writer (connectivity_matrix, connectivity_threshold); - set_optional_outputs (writer); - writer.save (argument[2]); + Fixel::Matrix::Writer writer(connectivity_matrix, connectivity_threshold); + set_optional_outputs(writer); + writer.save(argument[2]); } else { - auto connectivity_matrix = Fixel::Matrix::generate_unweighted (argument[1], - index_image, - fixel_mask, - angular_threshold); - - Fixel::Matrix::Writer writer (connectivity_matrix, connectivity_threshold); - set_optional_outputs (writer); - writer.save (argument[2]); + auto connectivity_matrix = + Fixel::Matrix::generate_unweighted(argument[1], index_image, fixel_mask, angular_threshold); + Fixel::Matrix::Writer writer(connectivity_matrix, connectivity_threshold); + set_optional_outputs(writer); + writer.save(argument[2]); } - } - diff --git a/cmd/fixelconvert.cpp b/cmd/fixelconvert.cpp index 052db82bda..e14f34156b 100644 --- a/cmd/fixelconvert.cpp +++ b/cmd/fixelconvert.cpp @@ -27,9 +27,8 @@ #include "fixel/loop.h" #include "fixel/legacy/fixel_metric.h" -#include "fixel/legacy/keys.h" #include "fixel/legacy/image.h" - +#include "fixel/legacy/keys.h" using namespace MR; using namespace App; @@ -37,100 +36,94 @@ using namespace App; using Fixel::index_type; using Fixel::Legacy::FixelMetric; - -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Convert between the old format fixel image (.msf / .msh) and the new fixel directory format"; DESCRIPTION - + Fixel::format_description; + +Fixel::format_description; EXAMPLES - + Example ("Convert from the old file format to the new directory format", - "fixelconvert old_fixels.msf new_fixels/ -out_size", - "This performs a simple conversion from old to new format, and " - "additionally writes the contents of the \"size\" field within " - "old-format fixel images stored using the \"FixelMetric\" class " - "(likely all of them) as an additional fixel data file.") - - + Example ("Convert multiple files from old to new format, preserving fixel correspondence", - "for_each *.msf : fixelconvert IN NAME_new/ -template template_fixels/", - "In this example, the for_each script is used to execute the fixelconvert " - "command once for each of a series of input files in the old fixel format, " - "generating a new output fixel directory for each." - "Importantly here though, the -template option is used to ensure that the " - "ordering of fixels within these output directories is identical, such that " - "fixel data files can be exchanged between them (e.g. accumulating fixel " - "data files across subjects into a single template fixel directory") - - + Example ("Convert from the new directory format to the old file format", - "fixelconvert new_fixels/ old_fixels.msf -value parameter.mif -in_size new_fixels/afd.mif", - "Conversion from the new directory format will contain the value 1.0 " - "for all output fixels in both the \"size\" and \"value\" fields of the " - "\"FixelMetric\" class, unless the -in_size and/or -value options are " - "used respectively to indicate which fixel data files should be used as " - "the source(s) of this information."); + +Example("Convert from the old file format to the new directory format", + "fixelconvert old_fixels.msf new_fixels/ -out_size", + "This performs a simple conversion from old to new format, and " + "additionally writes the contents of the \"size\" field within " + "old-format fixel images stored using the \"FixelMetric\" class " + "(likely all of them) as an additional fixel data file.") + + + Example("Convert multiple files from old to new format, preserving fixel correspondence", + "for_each *.msf : fixelconvert IN NAME_new/ -template template_fixels/", + "In this example, the for_each script is used to execute the fixelconvert " + "command once for each of a series of input files in the old fixel format, " + "generating a new output fixel directory for each." + "Importantly here though, the -template option is used to ensure that the " + "ordering of fixels within these output directories is identical, such that " + "fixel data files can be exchanged between them (e.g. accumulating fixel " + "data files across subjects into a single template fixel directory") + + + Example("Convert from the new directory format to the old file format", + "fixelconvert new_fixels/ old_fixels.msf -value parameter.mif -in_size new_fixels/afd.mif", + "Conversion from the new directory format will contain the value 1.0 " + "for all output fixels in both the \"size\" and \"value\" fields of the " + "\"FixelMetric\" class, unless the -in_size and/or -value options are " + "used respectively to indicate which fixel data files should be used as " + "the source(s) of this information."); ARGUMENTS - + Argument ("fixel_in", "the input fixel file / directory.").type_various() - + Argument ("fixel_out", "the output fixel file / directory.").type_various(); + +Argument("fixel_in", "the input fixel file / directory.").type_various() + + Argument("fixel_out", "the output fixel file / directory.").type_various(); OPTIONS - + OptionGroup ("Options for converting from old to new format") - + Option ("name", "assign a different name to the value field output (Default: value). Do not include the file extension.") - + Argument ("string").type_text() - + Option ("nii", "output the index, directions and data file in NIfTI format instead of .mif") - + Option ("out_size", "also output the 'size' field from the old format") - + Option ("template", "specify an existing fixel directory (in the new format) to which the new output should conform") - + Argument ("path").type_directory_in() - - + OptionGroup ("Options for converting from new to old format") - + Option ("value", "nominate the data file to import to the 'value' field in the old format") - + Argument ("path").type_file_in() - + Option ("in_size", "import data for the 'size' field in the old format") - + Argument ("path").type_file_in(); - + +OptionGroup("Options for converting from old to new format") + + Option("name", + "assign a different name to the value field output (Default: value). Do not include the file extension.") + + Argument("string").type_text() + + Option("nii", "output the index, directions and data file in NIfTI format instead of .mif") + + Option("out_size", "also output the 'size' field from the old format") + + Option("template", + "specify an existing fixel directory (in the new format) to which the new output should conform") + + Argument("path").type_directory_in() + + + OptionGroup("Options for converting from new to old format") + + Option("value", "nominate the data file to import to the 'value' field in the old format") + + Argument("path").type_file_in() + Option("in_size", "import data for the 'size' field in the old format") + + Argument("path").type_file_in(); } +void convert_old2new() { + Header header(Header::open(argument[0])); + header.keyval().erase(Fixel::Legacy::name_key); + header.keyval().erase(Fixel::Legacy::size_key); + Fixel::Legacy::Image input(argument[0]); + const std::string file_extension = get_options("nii").size() ? ".nii" : ".mif"; -void convert_old2new () -{ - Header header (Header::open (argument[0])); - header.keyval().erase (Fixel::Legacy::name_key); - header.keyval().erase (Fixel::Legacy::size_key); - - Fixel::Legacy::Image input (argument[0]); - - const std::string file_extension = get_options ("nii").size() ? ".nii" : ".mif"; - - std::string value_name ("value"); - auto opt = get_options ("name"); + std::string value_name("value"); + auto opt = get_options("name"); if (opt.size()) - value_name = std::string (opt[0][0]); + value_name = std::string(opt[0][0]); - const bool output_size = get_options ("out_size").size(); + const bool output_size = get_options("out_size").size(); const std::string output_fixel_directory = argument[1]; - Fixel::check_fixel_directory (output_fixel_directory, true, true); + Fixel::check_fixel_directory(output_fixel_directory, true, true); index_type fixel_count = 0; - for (auto i = Loop (input) (input); i; ++i) + for (auto i = Loop(input)(input); i; ++i) fixel_count += input.value().size(); - Header data_header (header); + Header data_header(header); data_header.ndim() = 3; data_header.size(0) = fixel_count; data_header.size(1) = 1; data_header.size(2) = 1; - data_header.datatype () = DataType::Float32; - data_header.datatype ().set_byte_order_native(); + data_header.datatype() = DataType::Float32; + data_header.datatype().set_byte_order_native(); - Header directions_header (data_header); + Header directions_header(data_header); directions_header.size(1) = 3; header.keyval()[Fixel::n_fixels_key] = str(fixel_count); @@ -139,31 +132,33 @@ void convert_old2new () header.datatype() = DataType::from(); header.datatype().set_byte_order_native(); - auto index_image = Image::create (Path::join (output_fixel_directory, "index" + file_extension), header); - auto directions_image = Image::create (Path::join (output_fixel_directory, "directions" + file_extension), directions_header).with_direct_io(); - auto value_image = Image::create (Path::join (output_fixel_directory, value_name + file_extension), data_header); + auto index_image = Image::create(Path::join(output_fixel_directory, "index" + file_extension), header); + auto directions_image = + Image::create(Path::join(output_fixel_directory, "directions" + file_extension), directions_header) + .with_direct_io(); + auto value_image = Image::create(Path::join(output_fixel_directory, value_name + file_extension), data_header); Image size_image; if (output_size) - size_image = Image::create (Path::join (output_fixel_directory, "size" + file_extension), data_header); + size_image = Image::create(Path::join(output_fixel_directory, "size" + file_extension), data_header); Image template_index_image; Image template_directions_image; - opt = get_options ("template"); + opt = get_options("template"); if (opt.size()) { - Fixel::check_fixel_directory (opt[0][0]); - template_index_image = Fixel::find_index_header (opt[0][0]).get_image(); - check_dimensions (index_image, template_index_image); - template_directions_image = Fixel::find_directions_header (opt[0][0]).get_image(); + Fixel::check_fixel_directory(opt[0][0]); + template_index_image = Fixel::find_index_header(opt[0][0]).get_image(); + check_dimensions(index_image, template_index_image); + template_directions_image = Fixel::find_directions_header(opt[0][0]).get_image(); } index_type offset = 0; - for (auto i = Loop ("converting fixel format", input, 0, 3) (input, index_image); i; ++i) { + for (auto i = Loop("converting fixel format", input, 0, 3)(input, index_image); i; ++i) { const index_type num_fixels = input.value().size(); if (template_index_image.valid()) { - assign_pos_of (index_image).to (template_index_image); + assign_pos_of(index_image).to(template_index_image); template_index_image.index(3) = 0; if (template_index_image.value() != num_fixels) - throw Exception ("mismatch in number of fixels between input and template images"); + throw Exception("mismatch in number of fixels between input and template images"); template_index_image.index(3) = 1; offset = template_index_image.value(); } @@ -184,8 +179,8 @@ void convert_old2new () template_directions_image.index(1) = axis; template_dir[axis] = template_directions_image.value(); } - if (input.value()[f].dir.dot (template_dir) < 0.999) - throw Exception ("mismatch in fixel directions between input and template images"); + if (input.value()[f].dir.dot(template_dir) < 0.999) + throw Exception("mismatch in fixel directions between input and template images"); } value_image.index(0) = offset; value_image.value() = input.value()[f].value; @@ -198,39 +193,36 @@ void convert_old2new () } } - - -void convert_new2old () -{ +void convert_new2old() { const std::string input_fixel_directory = argument[0]; - auto opt = get_options ("value"); + auto opt = get_options("value"); if (!opt.size()) - throw Exception ("for converting from new to old formats, option -value is compulsory"); - const std::string value_path = get_options ("value")[0][0]; - opt = get_options ("in_size"); + throw Exception("for converting from new to old formats, option -value is compulsory"); + const std::string value_path = get_options("value")[0][0]; + opt = get_options("in_size"); const std::string size_path = opt.size() ? std::string(opt[0][0]) : ""; - Header H_index = Fixel::find_index_header (input_fixel_directory); - Header H_dirs = Fixel::find_directions_header (input_fixel_directory); - vector
H_data = Fixel::find_data_headers (input_fixel_directory, H_index, false); + Header H_index = Fixel::find_index_header(input_fixel_directory); + Header H_dirs = Fixel::find_directions_header(input_fixel_directory); + vector
H_data = Fixel::find_data_headers(input_fixel_directory, H_index, false); size_t size_index = H_data.size(), value_index = H_data.size(); for (size_t i = 0; i != H_data.size(); ++i) { - if (Path::basename (H_data[i].name()) == Path::basename (value_path)) + if (Path::basename(H_data[i].name()) == Path::basename(value_path)) value_index = i; - if (Path::basename (H_data[i].name()) == Path::basename (size_path)) + if (Path::basename(H_data[i].name()) == Path::basename(size_path)) size_index = i; } if (value_index == H_data.size()) - throw Exception ("could not find image in input fixel directory corresponding to -value option"); + throw Exception("could not find image in input fixel directory corresponding to -value option"); - Header H_out (H_index); + Header H_out(H_index); H_out.ndim() = 3; H_out.datatype() = DataType::UInt64; H_out.datatype().set_byte_order_native(); H_out.keyval()[Fixel::Legacy::name_key] = str(typeid(FixelMetric).name()); H_out.keyval()[Fixel::Legacy::size_key] = str(sizeof(FixelMetric)); - Fixel::Legacy::Image out_image (argument[1], H_out); + Fixel::Legacy::Image out_image(argument[1], H_out); auto index_image = H_index.get_image(); auto dirs_image = H_dirs.get_image(); @@ -239,20 +231,20 @@ void convert_new2old () if (size_index != H_data.size()) size_image = H_data[size_index].get_image(); - for (auto l = Loop (out_image) (out_image, index_image); l; ++l) { + for (auto l = Loop(out_image)(out_image, index_image); l; ++l) { index_image.index(3) = 0; const index_type num_fixels = index_image.value(); - out_image.value().set_size (num_fixels); - for (auto f = Fixel::Loop (index_image) (dirs_image, value_image); f; ++f) { + out_image.value().set_size(num_fixels); + for (auto f = Fixel::Loop(index_image)(dirs_image, value_image); f; ++f) { // Construct the direction Eigen::Vector3f dir; for (size_t axis = 0; axis != 3; ++axis) { dirs_image.index(1) = axis; dir[axis] = dirs_image.value(); } - Fixel::Legacy::FixelMetric fixel (dir, value_image.value(), value_image.value()); + Fixel::Legacy::FixelMetric fixel(dir, value_image.value(), value_image.value()); if (size_image.valid()) { - assign_pos_of (value_image).to (size_image); + assign_pos_of(value_image).to(size_image); fixel.size = size_image.value(); } out_image.value()[f.fixel_index] = fixel; @@ -260,29 +252,21 @@ void convert_new2old () } } - - -bool is_old_format (const std::string& path) { - return (Path::has_suffix (path, ".msf") || Path::has_suffix (path, ".msh")); +bool is_old_format(const std::string &path) { + return (Path::has_suffix(path, ".msf") || Path::has_suffix(path, ".msh")); } - - -void run () -{ +void run() { // Detect in which direction the conversion is occurring - if (is_old_format (argument[0])) { - if (is_old_format (argument[1])) - throw Exception ("fixelconvert can only be used to convert between old and new fixel formats; NOT to convert images within the old format"); - convert_old2new (); + if (is_old_format(argument[0])) { + if (is_old_format(argument[1])) + throw Exception("fixelconvert can only be used to convert between old and new fixel formats; NOT to convert " + "images within the old format"); + convert_old2new(); } else { - if (!is_old_format (argument[1])) - throw Exception ("fixelconvert can only be used to convert between old and new fixel formats; NOT to convert within the new format"); - convert_new2old (); + if (!is_old_format(argument[1])) + throw Exception("fixelconvert can only be used to convert between old and new fixel formats; NOT to convert " + "within the new format"); + convert_new2old(); } } - - - - - diff --git a/cmd/fixelcorrespondence.cpp b/cmd/fixelcorrespondence.cpp index 22869cc30a..87280eeb5a 100644 --- a/cmd/fixelcorrespondence.cpp +++ b/cmd/fixelcorrespondence.cpp @@ -14,12 +14,12 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "progressbar.h" #include "algo/loop.h" -#include "image.h" +#include "command.h" #include "fixel/fixel.h" #include "fixel/helpers.h" +#include "image.h" +#include "progressbar.h" using namespace MR; using namespace App; @@ -28,60 +28,66 @@ using Fixel::index_type; #define DEFAULT_ANGLE_THRESHOLD 45.0 -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Obtain fixel-fixel correpondence between a subject fixel image and a template fixel mask"; DESCRIPTION - + "It is assumed that the subject image has already been spatially normalised and is aligned with the template. " - "The output fixel image will have the same fixels (and directions) of the template." + +"It is assumed that the subject image has already been spatially normalised and is aligned with the template. " + "The output fixel image will have the same fixels (and directions) of the template." - + Fixel::format_description; + + Fixel::format_description; ARGUMENTS - + Argument ("subject_data", "the input subject fixel data file. This should be a file inside the fixel directory").type_image_in () - + Argument ("template_directory", "the input template fixel directory.").type_directory_in() - + Argument ("output_directory", "the fixel directory where the output file will be written.").type_text() - + Argument ("output_data", "the name of the output fixel data file. This will be placed in the output fixel directory").type_text(); + +Argument("subject_data", "the input subject fixel data file. This should be a file inside the fixel directory") + .type_image_in() + + Argument("template_directory", "the input template fixel directory.").type_directory_in() + + Argument("output_directory", "the fixel directory where the output file will be written.").type_text() + + Argument("output_data", + "the name of the output fixel data file. This will be placed in the output fixel directory") + .type_text(); OPTIONS - + Option ("angle", "the max angle threshold for computing inter-subject fixel correspondence (Default: " + str(DEFAULT_ANGLE_THRESHOLD, 2) + " degrees)") - + Argument ("value").type_float (0.0, 90.0); + +Option("angle", + "the max angle threshold for computing inter-subject fixel correspondence (Default: " + + str(DEFAULT_ANGLE_THRESHOLD, 2) + " degrees)") + + Argument("value").type_float(0.0, 90.0); } +void run() { + const float angular_threshold = get_option_value("angle", DEFAULT_ANGLE_THRESHOLD); + const float angular_threshold_dp = cos(angular_threshold * (Math::pi / 180.0)); -void run () -{ - const float angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); - const float angular_threshold_dp = cos (angular_threshold * (Math::pi/180.0)); + const std::string input_file(argument[0]); + if (Path::is_dir(input_file)) + throw Exception("please input the specific fixel data file to be converted (not the fixel directory)"); - const std::string input_file (argument[0]); - if (Path::is_dir (input_file)) - throw Exception ("please input the specific fixel data file to be converted (not the fixel directory)"); - - auto subject_index = Fixel::find_index_header (Fixel::get_fixel_directory (input_file)).get_image(); - auto subject_directions = Fixel::find_directions_header (Fixel::get_fixel_directory (input_file)).get_image().with_direct_io(); + auto subject_index = Fixel::find_index_header(Fixel::get_fixel_directory(input_file)).get_image(); + auto subject_directions = + Fixel::find_directions_header(Fixel::get_fixel_directory(input_file)).get_image().with_direct_io(); if (input_file == subject_directions.name()) - throw Exception ("input fixel data file cannot be the directions file"); + throw Exception("input fixel data file cannot be the directions file"); - auto subject_data = Image::open (input_file); - Fixel::check_fixel_size (subject_index, subject_data); + auto subject_data = Image::open(input_file); + Fixel::check_fixel_size(subject_index, subject_data); - auto template_index = Fixel::find_index_header (argument[1]).get_image(); - auto template_directions = Fixel::find_directions_header (argument[1]).get_image().with_direct_io(); + auto template_index = Fixel::find_index_header(argument[1]).get_image(); + auto template_directions = Fixel::find_directions_header(argument[1]).get_image().with_direct_io(); - check_dimensions (subject_index, template_index); + check_dimensions(subject_index, template_index); std::string output_fixel_directory = argument[2]; - Fixel::copy_index_and_directions_file (argument[1], output_fixel_directory); + Fixel::copy_index_and_directions_file(argument[1], output_fixel_directory); - Header output_data_header (template_directions); + Header output_data_header(template_directions); output_data_header.size(1) = 1; - auto output_data = Image::create (Path::join (output_fixel_directory, argument[3]), output_data_header); + auto output_data = Image::create(Path::join(output_fixel_directory, argument[3]), output_data_header); - for (auto i = Loop ("mapping subject fixel data to template fixels", template_index, 0, 3)(template_index, subject_index); i; ++i) { + for (auto i = + Loop("mapping subject fixel data to template fixels", template_index, 0, 3)(template_index, subject_index); + i; + ++i) { template_index.index(3) = 0; index_type nfixels_template = template_index.value(); template_index.index(3) = 1; @@ -105,7 +111,7 @@ void run () templatedir.normalize(); Eigen::Vector3f subjectdir = subject_directions.row(1); subjectdir.normalize(); - float dp = abs (templatedir.dot (subjectdir)); + float dp = abs(templatedir.dot(subjectdir)); if (dp > largest_dp) { largest_dp = dp; index_of_closest_fixel = s; @@ -119,4 +125,3 @@ void run () } } } - diff --git a/cmd/fixelcrop.cpp b/cmd/fixelcrop.cpp index ea91eaed11..f99d4d7d98 100644 --- a/cmd/fixelcrop.cpp +++ b/cmd/fixelcrop.cpp @@ -14,9 +14,9 @@ * For more details, see http://www.mrtrix.org/. */ +#include "algo/loop.h" #include "command.h" #include "progressbar.h" -#include "algo/loop.h" #include "image.h" @@ -28,71 +28,72 @@ using namespace App; using Fixel::index_type; - -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) & Rami Tabarra (rami.tabarra@florey.edu.au)"; SYNOPSIS = "Crop/remove fixels from sparse fixel image using a binary fixel mask"; DESCRIPTION - + "The mask must be input as a fixel data file the same dimensions as the fixel data file(s) to be cropped." + +"The mask must be input as a fixel data file the same dimensions as the fixel data file(s) to be cropped." - + Fixel::format_description; + + Fixel::format_description; ARGUMENTS - + Argument ("input_fixel_directory", "input fixel directory, all data files and directions " - "file will be cropped and saved in the output fixel directory").type_directory_in() - + Argument ("input_fixel_mask", "the input fixel data file defining which fixels to crop. " - "Fixels with zero values will be removed").type_image_in () - + Argument ("output_fixel_directory", "the output directory to store the cropped directions and data files").type_directory_out(); + +Argument("input_fixel_directory", + "input fixel directory, all data files and directions " + "file will be cropped and saved in the output fixel directory") + .type_directory_in() + + Argument("input_fixel_mask", + "the input fixel data file defining which fixels to crop. " + "Fixels with zero values will be removed") + .type_image_in() + + Argument("output_fixel_directory", "the output directory to store the cropped directions and data files") + .type_directory_out(); } - - -void run () -{ +void run() { const auto in_directory = argument[0]; - Fixel::check_fixel_directory (in_directory); - Header in_index_header = Fixel::find_index_header (in_directory); - auto in_index_image = in_index_header.get_image (); + Fixel::check_fixel_directory(in_directory); + Header in_index_header = Fixel::find_index_header(in_directory); + auto in_index_image = in_index_header.get_image(); - auto mask_image = Image::open (argument[1]); - Fixel::check_fixel_size (in_index_image, mask_image); + auto mask_image = Image::open(argument[1]); + Fixel::check_fixel_size(in_index_image, mask_image); const auto out_fixel_directory = argument[2]; - Fixel::check_fixel_directory (out_fixel_directory, true, true); + Fixel::check_fixel_directory(out_fixel_directory, true, true); - Header out_header = Header (in_index_image); - index_type total_nfixels = Fixel::get_number_of_fixels (in_index_header); + Header out_header = Header(in_index_image); + index_type total_nfixels = Fixel::get_number_of_fixels(in_index_header); // We need to do a first pass of the mask image to determine the number of cropped fixels - for (auto l = Loop (0) (mask_image); l; ++l) { + for (auto l = Loop(0)(mask_image); l; ++l) { if (!mask_image.value()) - total_nfixels --; + total_nfixels--; } - out_header.keyval ()[Fixel::n_fixels_key] = str (total_nfixels); - auto out_index_image = Image::create (Path::join (out_fixel_directory, Path::basename (in_index_image.name())), out_header); - + out_header.keyval()[Fixel::n_fixels_key] = str(total_nfixels); + auto out_index_image = + Image::create(Path::join(out_fixel_directory, Path::basename(in_index_image.name())), out_header); // Open all data images and create output date images with size equal to expected number of fixels - vector
in_headers = Fixel::find_data_headers (in_directory, in_index_header, true); - vector > in_data_images; - vector > out_data_images; - for (auto& in_data_header : in_headers) { + vector
in_headers = Fixel::find_data_headers(in_directory, in_index_header, true); + vector> in_data_images; + vector> out_data_images; + for (auto &in_data_header : in_headers) { in_data_images.push_back(in_data_header.get_image().with_direct_io()); - check_dimensions (in_data_images.back(), mask_image, {0, 2}); + check_dimensions(in_data_images.back(), mask_image, {0, 2}); - Header out_data_header (in_data_header); - out_data_header.size (0) = total_nfixels; - out_data_images.push_back(Image::create (Path::join (out_fixel_directory, Path::basename (in_data_header.name())), - out_data_header).with_direct_io()); + Header out_data_header(in_data_header); + out_data_header.size(0) = total_nfixels; + out_data_images.push_back( + Image::create(Path::join(out_fixel_directory, Path::basename(in_data_header.name())), out_data_header) + .with_direct_io()); } - mask_image.index (1) = 0; + mask_image.index(1) = 0; index_type out_offset = 0; - for (auto l = Loop ("cropping fixel image", 0, 3) (in_index_image, out_index_image); l; ++l) { + for (auto l = Loop("cropping fixel image", 0, 3)(in_index_image, out_index_image); l; ++l) { in_index_image.index(3) = 0; index_type in_nfixels = in_index_image.value(); @@ -118,5 +119,4 @@ void run () out_index_image.value() = (out_nfixels) ? out_offset : 0; out_offset += out_nfixels; } - } diff --git a/cmd/fixelfilter.cpp b/cmd/fixelfilter.cpp index da8daf4504..e16e0c6366 100644 --- a/cmd/fixelfilter.cpp +++ b/cmd/fixelfilter.cpp @@ -14,15 +14,14 @@ * For more details, see http://www.mrtrix.org/. */ - #include "command.h" -#include "header.h" -#include "image.h" -#include "progressbar.h" #include "file/path.h" #include "file/utils.h" #include "fixel/fixel.h" #include "fixel/helpers.h" +#include "header.h" +#include "image.h" +#include "progressbar.h" #include "fixel/filter/base.h" #include "fixel/filter/connect.h" @@ -30,71 +29,66 @@ #include "fixel/matrix.h" #include "stats/cfe.h" - - using namespace MR; using namespace App; using namespace MR::Fixel; -const char* const filters[] = { "connect", "smooth", nullptr }; +const char *const filters[] = {"connect", "smooth", nullptr}; -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Perform filtering operations on fixel-based data"; DESCRIPTION - + "If the first input to the command is a specific fixel data file, then a filtered version of only that file " - "will be generated by the command. Alternatively, if the input is the location of a fixel directory, then the " - "command will create a duplicate of the fixel directory, and apply the specified filter operation to all " - "fixel data files within the directory." + +"If the first input to the command is a specific fixel data file, then a filtered version of only that file " + "will be generated by the command. Alternatively, if the input is the location of a fixel directory, then the " + "command will create a duplicate of the fixel directory, and apply the specified filter operation to all " + "fixel data files within the directory." - + Fixel::format_description; + + Fixel::format_description; ARGUMENTS - + Argument ("input", "the input: either a fixel data file, or a fixel directory (see Description)").type_various() - + Argument ("filter", "the filtering operation to perform; options are: " + join (filters, ", ")).type_choice (filters) - + Argument ("output", "the output: either a fixel data file, or a fixel directory (see Description)").type_various(); + +Argument("input", "the input: either a fixel data file, or a fixel directory (see Description)").type_various() + + Argument("filter", "the filtering operation to perform; options are: " + join(filters, ", ")) + .type_choice(filters) + + Argument("output", "the output: either a fixel data file, or a fixel directory (see Description)").type_various(); OPTIONS - + Option ("matrix", "provide a fixel-fixel connectivity matrix for filtering operations that require it").required() - + Argument ("file").type_directory_in() - - + OptionGroup ("Options specific to the \"connect\" filter") - + Option ("threshold_value", "specify a threshold for the input fixel data file values " - "(default = " + str(DEFAULT_FIXEL_CONNECT_VALUE_THRESHOLD) + ")") - + Argument ("value").type_float () - + Option ("threshold_connectivity", "specify a fixel-fixel connectivity threshold for connected-component analysis " - "(default = " + str(DEFAULT_FIXEL_CONNECT_CONNECTIVITY_THRESHOLD) + ")") - + Argument ("value").type_float (0.0) - - + OptionGroup ("Options specific to the \"smooth\" filter") - + Option ("fwhm", "the full-width half-maximum (FWHM) of the spatial component of the smoothing filter " - "(default = " + str(DEFAULT_FIXEL_SMOOTHING_FWHM) + "mm)") - + Argument ("value").type_float (0.0) - + Option ("minweight", "apply a minimum threshold to smoothing weights " - "(default = " + str(DEFAULT_FIXEL_SMOOTHING_MINWEIGHT) + ")") - + Argument ("value").type_float (0.0) - + Option ("mask", "only perform smoothing within a specified binary fixel mask") - + Argument ("image").type_image_in(); - + +Option("matrix", "provide a fixel-fixel connectivity matrix for filtering operations that require it").required() + + Argument("file").type_directory_in() + + + OptionGroup("Options specific to the \"connect\" filter") + + Option("threshold_value", + "specify a threshold for the input fixel data file values " + "(default = " + + str(DEFAULT_FIXEL_CONNECT_VALUE_THRESHOLD) + ")") + + Argument("value").type_float() + + Option("threshold_connectivity", + "specify a fixel-fixel connectivity threshold for connected-component analysis " + "(default = " + + str(DEFAULT_FIXEL_CONNECT_CONNECTIVITY_THRESHOLD) + ")") + + Argument("value").type_float(0.0) + + + OptionGroup("Options specific to the \"smooth\" filter") + + Option("fwhm", + "the full-width half-maximum (FWHM) of the spatial component of the smoothing filter " + "(default = " + + str(DEFAULT_FIXEL_SMOOTHING_FWHM) + "mm)") + + Argument("value").type_float(0.0) + + Option("minweight", + "apply a minimum threshold to smoothing weights " + "(default = " + + str(DEFAULT_FIXEL_SMOOTHING_MINWEIGHT) + ")") + + Argument("value").type_float(0.0) + + Option("mask", "only perform smoothing within a specified binary fixel mask") + Argument("image").type_image_in(); } - - using value_type = float; +void run() { - -void run() -{ - - std::set option_list { "threhsold_value", - "threshold_connectivity", - "fwhm", - "minweight", - "mask" }; + std::set option_list{"threhsold_value", "threshold_connectivity", "fwhm", "minweight", "mask"}; Image single_file; vector
multiple_files; @@ -104,86 +98,86 @@ void run() Header index_header; Header output_header; try { - index_header = Fixel::find_index_header (argument[0]); - multiple_files = Fixel::find_data_headers (argument[0], index_header); + index_header = Fixel::find_index_header(argument[0]); + multiple_files = Fixel::find_data_headers(argument[0], index_header); if (multiple_files.empty()) - throw Exception ("No fixel data files found in directory \"" + argument[0] + "\""); - output_header = Header (multiple_files[0]); + throw Exception("No fixel data files found in directory \"" + argument[0] + "\""); + output_header = Header(multiple_files[0]); } catch (...) { try { - index_header = Fixel::find_index_header (Fixel::get_fixel_directory (argument[0])); - single_file = Image::open (argument[0]); - Fixel::check_data_file (single_file); - output_header = Header (single_file); + index_header = Fixel::find_index_header(Fixel::get_fixel_directory(argument[0])); + single_file = Image::open(argument[0]); + Fixel::check_data_file(single_file); + output_header = Header(single_file); } catch (...) { - throw Exception ("Could not interpret first argument \"" + argument[0] + "\" as either a fixel data file, or a fixel directory"); + throw Exception("Could not interpret first argument \"" + argument[0] + + "\" as either a fixel data file, or a fixel directory"); } } - if (single_file.valid() && !Fixel::fixels_match (index_header, single_file)) - throw Exception ("File \"" + argument[0] + "\" is not a valid fixel data file (does not match corresponding index image)"); + if (single_file.valid() && !Fixel::fixels_match(index_header, single_file)) + throw Exception("File \"" + argument[0] + + "\" is not a valid fixel data file (does not match corresponding index image)"); - auto opt = get_options ("matrix"); - Fixel::Matrix::Reader matrix (opt[0][0]); + auto opt = get_options("matrix"); + Fixel::Matrix::Reader matrix(opt[0][0]); Image index_image = index_header.get_image(); - const size_t nfixels = Fixel::get_number_of_fixels (index_image); + const size_t nfixels = Fixel::get_number_of_fixels(index_image); if (nfixels != matrix.size()) - throw Exception ("Number of fixels in input (" + str(nfixels) + ") does not match number of fixels in connectivity matrix (" + str(matrix.size()) + ")"); + throw Exception("Number of fixels in input (" + str(nfixels) + + ") does not match number of fixels in connectivity matrix (" + str(matrix.size()) + ")"); switch (int(argument[1])) { - case 0: - { - const float value = get_option_value ("threshold_value", float(DEFAULT_FIXEL_CONNECT_VALUE_THRESHOLD)); - const float connect = get_option_value ("threshold_connectivity", float(DEFAULT_FIXEL_CONNECT_CONNECTIVITY_THRESHOLD)); - filter.reset (new Fixel::Filter::Connect (matrix, value, connect)); - output_header.datatype() = DataType::UInt32; - output_header.datatype().set_byte_order_native(); - option_list.erase ("threshold_value"); - option_list.erase ("threshold_connectivity"); - } - break; - case 1: - { - const float fwhm = get_option_value ("fwhm", float(DEFAULT_FIXEL_SMOOTHING_FWHM)); - const float threshold = get_option_value ("minweight", float(DEFAULT_FIXEL_SMOOTHING_MINWEIGHT)); - opt = get_options ("mask"); - if (opt.size()) { - Image mask_image = Image::open (opt[0][0]); - filter.reset (new Fixel::Filter::Smooth (index_image, matrix, mask_image, fwhm, threshold)); - } else { - filter.reset (new Fixel::Filter::Smooth (index_image, matrix, fwhm, threshold)); - } - option_list.erase ("fwhm"); - option_list.erase ("minweight"); - option_list.erase ("mask"); + case 0: { + const float value = get_option_value("threshold_value", float(DEFAULT_FIXEL_CONNECT_VALUE_THRESHOLD)); + const float connect = + get_option_value("threshold_connectivity", float(DEFAULT_FIXEL_CONNECT_CONNECTIVITY_THRESHOLD)); + filter.reset(new Fixel::Filter::Connect(matrix, value, connect)); + output_header.datatype() = DataType::UInt32; + output_header.datatype().set_byte_order_native(); + option_list.erase("threshold_value"); + option_list.erase("threshold_connectivity"); + } break; + case 1: { + const float fwhm = get_option_value("fwhm", float(DEFAULT_FIXEL_SMOOTHING_FWHM)); + const float threshold = get_option_value("minweight", float(DEFAULT_FIXEL_SMOOTHING_MINWEIGHT)); + opt = get_options("mask"); + if (opt.size()) { + Image mask_image = Image::open(opt[0][0]); + filter.reset(new Fixel::Filter::Smooth(index_image, matrix, mask_image, fwhm, threshold)); + } else { + filter.reset(new Fixel::Filter::Smooth(index_image, matrix, fwhm, threshold)); } - break; - default: - assert (0); + option_list.erase("fwhm"); + option_list.erase("minweight"); + option_list.erase("mask"); + } break; + default: + assert(0); } - } - for (const auto& i : option_list) { - if (get_options (i).size()) - WARN ("Option -" + i + " ignored; not relevant to " + filters[int(argument[1])] + " filter"); + for (const auto &i : option_list) { + if (get_options(i).size()) + WARN("Option -" + i + " ignored; not relevant to " + filters[int(argument[1])] + " filter"); } if (single_file.valid()) { - auto output_image = Image::create (argument[2], single_file); - CONSOLE (std::string ("Applying \"") + filters[argument[1]] + "\" operation to fixel data file \"" + single_file.name() + "\""); - (*filter) (single_file, output_image); + auto output_image = Image::create(argument[2], single_file); + CONSOLE(std::string("Applying \"") + filters[argument[1]] + "\" operation to fixel data file \"" + + single_file.name() + "\""); + (*filter)(single_file, output_image); } else { - Fixel::copy_index_and_directions_file (argument[0], argument[2]); - ProgressBar progress (std::string ("Applying \"") + filters[argument[1]] + "\" operation to " + str(multiple_files.size()) + " fixel data files", - multiple_files.size()); - for (auto& H : multiple_files) { + Fixel::copy_index_and_directions_file(argument[0], argument[2]); + ProgressBar progress(std::string("Applying \"") + filters[argument[1]] + "\" operation to " + + str(multiple_files.size()) + " fixel data files", + multiple_files.size()); + for (auto &H : multiple_files) { auto input_image = H.get_image(); - auto output_image = Image::create (Path::join (argument[2], Path::basename (H.name())), H); - (*filter) (input_image, output_image); + auto output_image = Image::create(Path::join(argument[2], Path::basename(H.name())), H); + (*filter)(input_image, output_image); ++progress; } } - } diff --git a/cmd/fixelreorient.cpp b/cmd/fixelreorient.cpp index c0ca902e88..25c624e888 100644 --- a/cmd/fixelreorient.cpp +++ b/cmd/fixelreorient.cpp @@ -14,11 +14,11 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "progressbar.h" +#include "adapter/jacobian.h" #include "algo/loop.h" +#include "command.h" #include "image.h" -#include "adapter/jacobian.h" +#include "progressbar.h" #include "registration/warp/helpers.h" #include "fixel/fixel.h" @@ -29,58 +29,63 @@ using namespace App; using Fixel::index_type; -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Reorient fixel directions"; DESCRIPTION - + "Reorientation is performed by transforming the vector representing " - "the fixel direction with the Jacobian (local affine transform) computed at each voxel in the warp, " - "then re-normalising the vector." + +"Reorientation is performed by transforming the vector representing " + "the fixel direction with the Jacobian (local affine transform) computed at each voxel in the warp, " + "then re-normalising the vector." - + Fixel::format_description; + + Fixel::format_description; ARGUMENTS - + Argument ("fixel_in", "the input fixel directory").type_directory_in() - + Argument ("warp", "a 4D deformation field used to perform reorientation. " - "Reorientation is performed by applying the Jacobian affine transform in each voxel in the warp, " - "then re-normalising the vector representing the fixel direction").type_image_in () - + Argument ("fixel_out", "the output fixel directory. If the the input and output directories are the same, the existing directions file will " - "be replaced (providing the -force option is supplied). If a new directory is supplied then the fixel directions and all " - "other fixel data will be copied to the new directory.").type_directory_out(); + +Argument("fixel_in", "the input fixel directory").type_directory_in() + + Argument("warp", + "a 4D deformation field used to perform reorientation. " + "Reorientation is performed by applying the Jacobian affine transform in each voxel in the warp, " + "then re-normalising the vector representing the fixel direction") + .type_image_in() + + Argument("fixel_out", + "the output fixel directory. If the the input and output directories are the same, the existing " + "directions file will " + "be replaced (providing the -force option is supplied). If a new directory is supplied then the fixel " + "directions and all " + "other fixel data will be copied to the new directory.") + .type_directory_out(); } - -void run () -{ +void run() { std::string input_fixel_directory = argument[0]; - Fixel::check_fixel_directory (input_fixel_directory); + Fixel::check_fixel_directory(input_fixel_directory); - auto input_index_image = Fixel::find_index_header (input_fixel_directory).get_image (); + auto input_index_image = Fixel::find_index_header(input_fixel_directory).get_image(); - Header warp_header = Header::open (argument[1]); - Registration::Warp::check_warp (warp_header); - check_dimensions (input_index_image, warp_header, 0, 3); - Adapter::Jacobian > jacobian (warp_header.get_image()); + Header warp_header = Header::open(argument[1]); + Registration::Warp::check_warp(warp_header); + check_dimensions(input_index_image, warp_header, 0, 3); + Adapter::Jacobian> jacobian(warp_header.get_image()); std::string output_fixel_directory = argument[2]; - Fixel::check_fixel_directory (output_fixel_directory, true); + Fixel::check_fixel_directory(output_fixel_directory, true); // scratch buffer so inplace reorientation can be performed if desired Image input_directions_image; std::string output_directions_filename; { - auto tmp = Fixel::find_directions_header (input_fixel_directory).get_image(); + auto tmp = Fixel::find_directions_header(input_fixel_directory).get_image(); input_directions_image = Image::scratch(tmp); - threaded_copy (tmp, input_directions_image); + threaded_copy(tmp, input_directions_image); output_directions_filename = Path::basename(tmp.name()); } - auto output_directions_image = Image::create (Path::join(output_fixel_directory, output_directions_filename), input_directions_image).with_direct_io(); + auto output_directions_image = + Image::create(Path::join(output_fixel_directory, output_directions_filename), input_directions_image) + .with_direct_io(); - for (auto i = Loop ("reorienting fixel directions", input_index_image, 0, 3)(input_index_image, jacobian); i; ++i) { + for (auto i = Loop("reorienting fixel directions", input_index_image, 0, 3)(input_index_image, jacobian); i; ++i) { input_index_image.index(3) = 0; index_type num_fixels_in_voxel = input_index_image.value(); if (num_fixels_in_voxel) { @@ -90,14 +95,13 @@ void run () for (index_type f = 0; f < num_fixels_in_voxel; ++f) { input_directions_image.index(0) = index + f; output_directions_image.index(0) = index + f; - output_directions_image.row(1) = (transform * Eigen::Vector3f (input_directions_image.row(1))).normalized(); + output_directions_image.row(1) = (transform * Eigen::Vector3f(input_directions_image.row(1))).normalized(); } } } if (output_fixel_directory != input_fixel_directory) { - Fixel::copy_index_file (input_fixel_directory, output_fixel_directory); - Fixel::copy_all_data_files (input_fixel_directory, output_fixel_directory); + Fixel::copy_index_file(input_fixel_directory, output_fixel_directory); + Fixel::copy_all_data_files(input_fixel_directory, output_fixel_directory); } } - diff --git a/cmd/fod2dec.cpp b/cmd/fod2dec.cpp index 8b63ffa70b..2607c7cc15 100644 --- a/cmd/fod2dec.cpp +++ b/cmd/fod2dec.cpp @@ -16,15 +16,15 @@ #include -#include "command.h" -#include "progressbar.h" #include "algo/threaded_loop.h" -#include "image.h" -#include "math/sphere.h" -#include "math/SH.h" +#include "command.h" #include "dwi/directions/predefined.h" #include "filter/reslice.h" +#include "image.h" #include "interp/cubic.h" +#include "math/SH.h" +#include "math/sphere.h" +#include "progressbar.h" using namespace MR; using namespace App; @@ -34,93 +34,101 @@ using namespace App; #define DEFAULT_LUM_CB 0.2 #define DEFAULT_LUM_GAMMA 2.2 -void usage () -{ +void usage() { AUTHOR = "Thijs Dhollander (thijs.dhollander@gmail.com)"; - COPYRIGHT = - "Copyright (C) 2014 The Florey Institute of Neuroscience and Mental Health, Melbourne, Australia. " - "This is free software; see the source for copying conditions. " - "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."; + COPYRIGHT = "Copyright (C) 2014 The Florey Institute of Neuroscience and Mental Health, Melbourne, Australia. " + "This is free software; see the source for copying conditions. " + "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."; - SYNOPSIS = "Generate FOD-based DEC maps, with optional panchromatic sharpening and/or luminance/perception correction"; + SYNOPSIS = + "Generate FOD-based DEC maps, with optional panchromatic sharpening and/or luminance/perception correction"; DESCRIPTION - + "By default, the FOD-based DEC is weighted by the integral of the FOD. To weight by another scalar map, use the -contrast option. This option can also be used for panchromatic sharpening, e.g., by supplying a T1 (or other sensible) anatomical volume with a higher spatial resolution."; + +"By default, the FOD-based DEC is weighted by the integral of the FOD. To weight by another scalar map, use the " + "-contrast option. This option can also be used for panchromatic sharpening, e.g., by supplying a T1 (or other " + "sensible) anatomical volume with a higher spatial resolution."; REFERENCES - + "Dhollander T, Smith RE, Tournier JD, Jeurissen B, Connelly A. " // Internal - "Time to move on: an FOD-based DEC map to replace DTI's trademark DEC FA. " - "Proc Intl Soc Mag Reson Med, 2015, 23, 1027" - + "Dhollander T, Raffelt D, Smith RE, Connelly A. " // Internal + +"Dhollander T, Smith RE, Tournier JD, Jeurissen B, Connelly A. " // Internal + "Time to move on: an FOD-based DEC map to replace DTI's trademark DEC FA. " + "Proc Intl Soc Mag Reson Med, 2015, 23, 1027" + + "Dhollander T, Raffelt D, Smith RE, Connelly A. " // Internal "Panchromatic sharpening of FOD-based DEC maps by structural T1 information. " "Proc Intl Soc Mag Reson Med, 2015, 23, 566"; ARGUMENTS - + Argument ("input","The input FOD image (spherical harmonic coefficients).").type_image_in () - + Argument ("output","The output DEC image (weighted RGB triplets).").type_image_out (); + +Argument("input", "The input FOD image (spherical harmonic coefficients).").type_image_in() + + Argument("output", "The output DEC image (weighted RGB triplets).").type_image_out(); OPTIONS - + Option ("mask","Only perform DEC computation within the specified mask image.") - + Argument ("image").type_image_in() - - + Option ("contrast","Weight the computed DEC map by the provided image contrast. If the contrast has a different image grid, the DEC map is first resliced and renormalised. To achieve panchromatic sharpening, provide an image with a higher spatial resolution than the input FOD image; e.g., a T1 anatomical volume. Only the DEC is subject to the mask, so as to allow for partial colouring of the contrast image. \nDefault when this option is *not* provided: integral of input FOD, subject to the same mask/threshold as used for DEC computation.") - + Argument ("image").type_image_in() - - + Option ("lum","Correct for luminance/perception, using default values Cr,Cg,Cb = " + str(DEFAULT_LUM_CR, 2) + "," + str(DEFAULT_LUM_CG, 2) + "," + str(DEFAULT_LUM_CB, 2) + " and gamma = " + str(DEFAULT_LUM_GAMMA, 2) + " (*not* correcting is the theoretical equivalent of Cr,Cg,Cb = 1,1,1 and gamma = 2).") - - + Option ("lum_coefs","The coefficients Cr,Cg,Cb to correct for luminance/perception. \nNote: this implicitly switches on luminance/perception correction, using a default gamma = " + str(DEFAULT_LUM_GAMMA, 2) + " unless specified otherwise.") - + Argument ("values").type_sequence_float() - - + Option ("lum_gamma","The gamma value to correct for luminance/perception. \nNote: this implicitly switches on luminance/perception correction, using a default Cr,Cg,Cb = " + str(DEFAULT_LUM_CR, 2) + "," + str(DEFAULT_LUM_CG, 2) + "," + str(DEFAULT_LUM_CB, 2) + " unless specified otherwise.") - + Argument ("value").type_float() - - + Option ("threshold","FOD amplitudes below the threshold value are considered zero.") - + Argument ("value").type_float() - - + Option ("no_weight","Do not weight the DEC map; just output the unweighted colours. Reslicing and renormalising of colours will still happen when providing the -contrast option as a template."); - + +Option("mask", "Only perform DEC computation within the specified mask image.") + Argument("image").type_image_in() + + + + Option( + "contrast", + "Weight the computed DEC map by the provided image contrast. If the contrast has a different image grid, the " + "DEC map is first resliced and renormalised. To achieve panchromatic sharpening, provide an image with a " + "higher spatial resolution than the input FOD image; e.g., a T1 anatomical volume. Only the DEC is subject " + "to the mask, so as to allow for partial colouring of the contrast image. \nDefault when this option is " + "*not* provided: integral of input FOD, subject to the same mask/threshold as used for DEC computation.") + + Argument("image").type_image_in() + + + Option("lum", + "Correct for luminance/perception, using default values Cr,Cg,Cb = " + str(DEFAULT_LUM_CR, 2) + "," + + str(DEFAULT_LUM_CG, 2) + "," + str(DEFAULT_LUM_CB, 2) + " and gamma = " + str(DEFAULT_LUM_GAMMA, 2) + + " (*not* correcting is the theoretical equivalent of Cr,Cg,Cb = 1,1,1 and gamma = 2).") + + + Option("lum_coefs", + "The coefficients Cr,Cg,Cb to correct for luminance/perception. \nNote: this implicitly switches on " + "luminance/perception correction, using a default gamma = " + + str(DEFAULT_LUM_GAMMA, 2) + " unless specified otherwise.") + + Argument("values").type_sequence_float() + + + Option("lum_gamma", + "The gamma value to correct for luminance/perception. \nNote: this implicitly switches on " + "luminance/perception correction, using a default Cr,Cg,Cb = " + + str(DEFAULT_LUM_CR, 2) + "," + str(DEFAULT_LUM_CG, 2) + "," + str(DEFAULT_LUM_CB, 2) + + " unless specified otherwise.") + + Argument("value").type_float() + + + Option("threshold", "FOD amplitudes below the threshold value are considered zero.") + + Argument("value").type_float() + + + Option("no_weight", + "Do not weight the DEC map; just output the unweighted colours. Reslicing and renormalising of colours " + "will still happen when providing the -contrast option as a template."); } - using value_type = float; -const value_type UNIT = 1.0 / std::sqrt(3.0); // component of 3D unit vector wrt L2-norm - - -class DecTransform { +const value_type UNIT = 1.0 / std::sqrt(3.0); // component of 3D unit vector wrt L2-norm - public: +class DecTransform { +public: Eigen::MatrixXd sht; Eigen::Matrix decs; double thresh; - DecTransform (int lmax, const Eigen::Matrix& dirs, double thresh) : - sht (Math::SH::init_transform(dirs, lmax)), - decs (Math::Sphere::spherical2cartesian(dirs).cwiseAbs()), - thresh (thresh) { } - + DecTransform(int lmax, const Eigen::Matrix &dirs, double thresh) + : sht(Math::SH::init_transform(dirs, lmax)), + decs(Math::Sphere::spherical2cartesian(dirs).cwiseAbs()), + thresh(thresh) {} }; -class DecComputer { - - private: +class DecComputer { - const DecTransform& dectrans; +private: + const DecTransform &dectrans; Image mask_img; Image int_img; Eigen::VectorXd amps, fod; - public: +public: + DecComputer(const DecTransform &dectrans, Image &mask_img, Image &int_img) + : dectrans(dectrans), mask_img(mask_img), int_img(int_img), amps(dectrans.sht.rows()), fod(dectrans.sht.cols()) {} - DecComputer (const DecTransform& dectrans, Image& mask_img, Image& int_img) : - dectrans (dectrans), - mask_img (mask_img), - int_img (int_img), - amps (dectrans.sht.rows()), - fod (dectrans.sht.cols()) { } - - void operator() (Image& fod_img, Image& dec_img) { + void operator()(Image &fod_img, Image &dec_img) { if (mask_img.valid()) { assign_pos_of(fod_img, 0, 3).to(mask_img); @@ -155,62 +163,53 @@ class DecComputer { assign_pos_of(fod_img, 0, 3).to(int_img); int_img.value() = (ampsum / amps.rows()) * 4.0 * Math::pi; } - } - }; -class DecWeighter { - - private: +class DecWeighter { +private: Eigen::Array coefs; value_type gamma; Image w_img; value_type grey; - public: +public: + DecWeighter(Eigen::Matrix coefs, value_type gamma, Image &w_img) + : coefs(coefs), gamma(gamma), w_img(w_img), grey(1.0 / std::pow(coefs.sum(), 1.0 / gamma)) {} - DecWeighter (Eigen::Matrix coefs, value_type gamma, Image& w_img) : - coefs (coefs), - gamma (gamma), - w_img (w_img), - grey (1.0 / std::pow(coefs.sum(), 1.0 / gamma)) {} - - void operator() (Image& dec_img) { + void operator()(Image &dec_img) { value_type w = 1.0; if (w_img.valid()) { assign_pos_of(dec_img, 0, 3).to(w_img); w = w_img.value(); if (w <= 0.0) { - for (auto l = Loop (3) (dec_img); l; ++l) + for (auto l = Loop(3)(dec_img); l; ++l) dec_img.value() = 0.0; return; } } Eigen::Array dec; - for (auto l = Loop (3) (dec_img); l; ++l) + for (auto l = Loop(3)(dec_img); l; ++l) dec[dec_img.index(3)] = dec_img.value(); dec = dec.cwiseMax(0.0); - value_type br = std::pow((dec.pow(gamma) * coefs).sum() , 1.0 / gamma); + value_type br = std::pow((dec.pow(gamma) * coefs).sum(), 1.0 / gamma); if (br == 0.0) dec.fill(grey * w); else dec = dec * (w / br); - for (auto l = Loop (3) (dec_img); l; ++l) - dec_img.value() = dec[dec_img.index(3)]; - + for (auto l = Loop(3)(dec_img); l; ++l) + dec_img.value() = dec[dec_img.index(3)]; } - }; -void run () { +void run() { auto fod_hdr = Header::open(argument[0]); Math::SH::check(fod_hdr); @@ -219,24 +218,29 @@ void run () { auto optm = get_options("mask"); if (optm.size()) { mask_hdr = Header::open(optm[0][0]); - check_dimensions (mask_hdr, fod_hdr, 0, 3); + check_dimensions(mask_hdr, fod_hdr, 0, 3); } - float thresh = get_option_value ("threshold", NAN); + float thresh = get_option_value("threshold", NAN); bool needtolum = false; - Eigen::Array coefs (1.0, 1.0, 1.0); + Eigen::Array coefs(1.0, 1.0, 1.0); value_type gamma = 2.0; auto optlc = get_options("lum_coefs"); auto optlg = get_options("lum_gamma"); if (get_options("lum").size() || optlc.size() || optlg.size()) { needtolum = true; - coefs << DEFAULT_LUM_CR , DEFAULT_LUM_CG , DEFAULT_LUM_CB; gamma = DEFAULT_LUM_GAMMA; + coefs << DEFAULT_LUM_CR, DEFAULT_LUM_CG, DEFAULT_LUM_CB; + gamma = DEFAULT_LUM_GAMMA; if (optlc.size()) { auto lc = parse_floats(optlc[0][0]); if (lc.size() != 3) - throw Exception ("expecting exactly 3 coefficients for the lum_coefs option, provided as a comma-separated list Cr,Cg,Cb ; e.g., " + str(DEFAULT_LUM_CR, 2) + "," + str(DEFAULT_LUM_CG, 2) + "," + str(DEFAULT_LUM_CB, 2) + ""); - coefs(0) = lc[0]; coefs(1) = lc[1]; coefs(2) = lc[2]; + throw Exception("expecting exactly 3 coefficients for the lum_coefs option, provided as a comma-separated list " + "Cr,Cg,Cb ; e.g., " + + str(DEFAULT_LUM_CR, 2) + "," + str(DEFAULT_LUM_CG, 2) + "," + str(DEFAULT_LUM_CB, 2) + ""); + coefs(0) = lc[0]; + coefs(1) = lc[1]; + coefs(2) = lc[2]; } if (optlg.size()) gamma = optlg[0][0]; @@ -244,12 +248,11 @@ void run () { bool needtoslice = false; auto map_hdr = Header(); - auto opto = get_options ("contrast"); + auto opto = get_options("contrast"); if (opto.size()) { map_hdr = Header::open(opto[0][0]); - if (!dimensions_match(map_hdr, fod_hdr, 0, 3) || - !spacings_match(map_hdr, fod_hdr, 0, 3) || - !map_hdr.transform().isApprox(fod_hdr.transform(),1e-42)) + if (!dimensions_match(map_hdr, fod_hdr, 0, 3) || !spacings_match(map_hdr, fod_hdr, 0, 3) || + !map_hdr.transform().isApprox(fod_hdr.transform(), 1e-42)) needtoslice = true; } @@ -265,8 +268,8 @@ void run () { auto dec_hdr = Header(fod_img); dec_hdr.ndim() = 4; dec_hdr.size(3) = 3; - Stride::set (dec_hdr, Stride::contiguous_along_axis (3, dec_hdr)); - dec_img = Image::scratch(dec_hdr,"DEC map"); + Stride::set(dec_hdr, Stride::contiguous_along_axis(3, dec_hdr)); + dec_img = Image::scratch(dec_hdr, "DEC map"); Eigen::Matrix dirs = DWI::Directions::tesselation_1281(); @@ -277,32 +280,31 @@ void run () { if (!get_options("no_weight").size() && !map_hdr) { auto int_hdr = Header(dec_img); int_hdr.size(3) = 1; - w_img = Image::scratch(int_hdr,"FOD integral map"); + w_img = Image::scratch(int_hdr, "FOD integral map"); } - ThreadedLoop ("computing colours", fod_img, 0, 3) - .run (DecComputer (DecTransform (Math::SH::LforN(fod_img.size(3)), dirs, thresh), mask_img, w_img), fod_img, dec_img); + ThreadedLoop("computing colours", fod_img, 0, 3) + .run(DecComputer(DecTransform(Math::SH::LforN(fod_img.size(3)), dirs, thresh), mask_img, w_img), + fod_img, + dec_img); } auto out_hdr = map_hdr.valid() ? Header(map_hdr) : Header(dec_img); out_hdr.datatype() = DataType::Float32; out_hdr.ndim() = 4; out_hdr.size(3) = 3; - Stride::set (out_hdr, Stride::contiguous_along_axis (3, out_hdr)); - out_img = Image::create(argument[1],out_hdr); + Stride::set(out_hdr, Stride::contiguous_along_axis(3, out_hdr)); + out_img = Image::create(argument[1], out_hdr); if (needtoslice) - Filter::reslice (dec_img, out_img, Adapter::NoTransform, Adapter::AutoOverSample, UNIT); + Filter::reslice(dec_img, out_img, Adapter::NoTransform, Adapter::AutoOverSample, UNIT); else - copy (dec_img, out_img); + copy(dec_img, out_img); } if (!get_options("no_weight").size() && map_hdr.valid()) w_img = map_hdr.get_image(); if (w_img.valid() || needtolum || needtoslice) - ThreadedLoop ("(re)weighting", out_img, 0, 3, 2) - .run (DecWeighter (coefs, gamma, w_img), out_img); - + ThreadedLoop("(re)weighting", out_img, 0, 3, 2).run(DecWeighter(coefs, gamma, w_img), out_img); } - diff --git a/cmd/fod2fixel.cpp b/cmd/fod2fixel.cpp index 00459eacca..38869ccdcf 100644 --- a/cmd/fod2fixel.cpp +++ b/cmd/fod2fixel.cpp @@ -26,14 +26,11 @@ #include "thread_queue.h" -#include "dwi/fmls.h" #include "dwi/directions/set.h" +#include "dwi/fmls.h" #include "file/path.h" - - - using namespace MR; using namespace MR::DWI; using namespace MR::DWI::FMLS; @@ -41,149 +38,132 @@ using namespace App; using Fixel::index_type; +const OptionGroup OutputOptions = + OptionGroup("Metric values for fixel-based sparse output images") -const OptionGroup OutputOptions = OptionGroup ("Metric values for fixel-based sparse output images") - - + Option ("afd", - "output the total Apparent Fibre Density per fixel (integral of FOD lobe)") - + Argument ("image").type_image_out() - - + Option ("peak_amp", - "output the amplitude of the FOD at the maximal peak per fixel") - + Argument ("image").type_image_out() + + Option("afd", "output the total Apparent Fibre Density per fixel (integral of FOD lobe)") + + Argument("image").type_image_out() - + Option ("disp", - "output a measure of dispersion per fixel as the ratio between FOD lobe integral and maximal peak amplitude") - + Argument ("image").type_image_out(); + + Option("peak_amp", "output the amplitude of the FOD at the maximal peak per fixel") + + Argument("image").type_image_out() + + + Option( + "disp", + "output a measure of dispersion per fixel as the ratio between FOD lobe integral and maximal peak amplitude") + + Argument("image").type_image_out(); - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Perform segmentation of continuous Fibre Orientation Distributions (FODs) to produce discrete fixels"; DESCRIPTION - + Fixel::format_description; + +Fixel::format_description; REFERENCES - + "* Reference for the FOD segmentation method:\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 2)" + +"* Reference for the FOD segmentation method:\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 2)" - + "* Reference for Apparent Fibre Density (AFD):\n" - "Raffelt, D.; Tournier, J.-D.; Rose, S.; Ridgway, G.R.; Henderson, R.; Crozier, S.; Salvado, O.; Connelly, A. " // Internal - "Apparent Fibre Density: a novel measure for the analysis of diffusion-weighted magnetic resonance images." - "Neuroimage, 2012, 15;59(4), 3976-94"; + + + "* Reference for Apparent Fibre Density (AFD):\n" + "Raffelt, D.; Tournier, J.-D.; Rose, S.; Ridgway, G.R.; Henderson, R.; Crozier, S.; Salvado, O.; Connelly, A. " // Internal + "Apparent Fibre Density: a novel measure for the analysis of diffusion-weighted magnetic resonance images." + "Neuroimage, 2012, 15;59(4), 3976-94"; ARGUMENTS - + Argument ("fod", "the input fod image.").type_image_in () - + Argument ("fixel_directory", "the output fixel directory").type_directory_out(); - + +Argument("fod", "the input fod image.").type_image_in() + + Argument("fixel_directory", "the output fixel directory").type_directory_out(); OPTIONS - + OutputOptions + +OutputOptions - + FMLSSegmentOption + + FMLSSegmentOption - + OptionGroup ("Other options for fod2fixel") + + OptionGroup("Other options for fod2fixel") - + Option ("mask", "only perform computation within the specified binary brain mask image.") - + Argument ("image").type_image_in() + + Option("mask", "only perform computation within the specified binary brain mask image.") + + Argument("image").type_image_in() - + Option ("maxnum", "maximum number of fixels to output for any particular voxel (default: no limit)") - + Argument ("number").type_integer(1) + + Option("maxnum", "maximum number of fixels to output for any particular voxel (default: no limit)") + + Argument("number").type_integer(1) - + Option ("nii", "output the directions and index file in nii format (instead of the default mif)") - - + Option ("dirpeak", "define the fixel direction as that of the lobe's maximal peak as opposed to its weighted mean direction (the default)"); + + Option("nii", "output the directions and index file in nii format (instead of the default mif)") + + Option("dirpeak", + "define the fixel direction as that of the lobe's maximal peak as opposed to its weighted mean " + "direction (the default)"); } +class Segmented_FOD_receiver { +public: + Segmented_FOD_receiver(const Header &header, const index_type maxnum = 0, bool dir_from_peak = false) + : H(header), fixel_count(0), max_per_voxel(maxnum), dir_from_peak(dir_from_peak) {} -class Segmented_FOD_receiver { + void commit(); - public: - Segmented_FOD_receiver (const Header& header, const index_type maxnum = 0, bool dir_from_peak = false) : - H (header), fixel_count (0), max_per_voxel (maxnum), dir_from_peak (dir_from_peak) { } - - void commit (); - - void set_fixel_directory_output (const std::string& path) { fixel_directory_path = path; } - void set_index_output (const std::string& path) { index_path = path; } - void set_directions_output (const std::string& path) { dir_path = path; } - void set_afd_output (const std::string& path) { afd_path = path; } - void set_peak_amp_output (const std::string& path) { peak_amp_path = path; } - void set_disp_output (const std::string& path) { disp_path = path; } - - bool operator() (const FOD_lobes&); - - - private: - - struct Primitive_FOD_lobe { - Eigen::Vector3f dir; - float integral; - float max_peak_amp; - Primitive_FOD_lobe (Eigen::Vector3f dir, float integral, float max_peak_amp) : - dir (dir), integral (integral), max_peak_amp (max_peak_amp) {} - }; - - - class Primitive_FOD_lobes : public vector { - public: - Primitive_FOD_lobes (const FOD_lobes& in, const index_type maxcount, bool dir_from_peak) : - vox (in.vox) - { - const index_type N = maxcount ? std::min (index_type(in.size()), maxcount) : in.size(); - for (index_type i = 0; i != N; ++i) { - const FOD_lobe& lobe (in[i]); - if (dir_from_peak) - this->emplace_back (lobe.get_peak_dir(0).cast(), lobe.get_integral(), lobe.get_max_peak_value()); - else - this->emplace_back (lobe.get_mean_dir().cast(), lobe.get_integral(), lobe.get_max_peak_value()); - } - } - Eigen::Array3i vox; - }; - - Header H; - std::string fixel_directory_path, index_path, dir_path, afd_path, peak_amp_path, disp_path; - vector lobes; - index_type fixel_count; - index_type max_per_voxel; - bool dir_from_peak; -}; + void set_fixel_directory_output(const std::string &path) { fixel_directory_path = path; } + void set_index_output(const std::string &path) { index_path = path; } + void set_directions_output(const std::string &path) { dir_path = path; } + void set_afd_output(const std::string &path) { afd_path = path; } + void set_peak_amp_output(const std::string &path) { peak_amp_path = path; } + void set_disp_output(const std::string &path) { disp_path = path; } + bool operator()(const FOD_lobes &); +private: + struct Primitive_FOD_lobe { + Eigen::Vector3f dir; + float integral; + float max_peak_amp; + Primitive_FOD_lobe(Eigen::Vector3f dir, float integral, float max_peak_amp) + : dir(dir), integral(integral), max_peak_amp(max_peak_amp) {} + }; + class Primitive_FOD_lobes : public vector { + public: + Primitive_FOD_lobes(const FOD_lobes &in, const index_type maxcount, bool dir_from_peak) : vox(in.vox) { + const index_type N = maxcount ? std::min(index_type(in.size()), maxcount) : in.size(); + for (index_type i = 0; i != N; ++i) { + const FOD_lobe &lobe(in[i]); + if (dir_from_peak) + this->emplace_back(lobe.get_peak_dir(0).cast(), lobe.get_integral(), lobe.get_max_peak_value()); + else + this->emplace_back(lobe.get_mean_dir().cast(), lobe.get_integral(), lobe.get_max_peak_value()); + } + } + Eigen::Array3i vox; + }; + + Header H; + std::string fixel_directory_path, index_path, dir_path, afd_path, peak_amp_path, disp_path; + vector lobes; + index_type fixel_count; + index_type max_per_voxel; + bool dir_from_peak; +}; -bool Segmented_FOD_receiver::operator() (const FOD_lobes& in) -{ +bool Segmented_FOD_receiver::operator()(const FOD_lobes &in) { if (in.size()) { - lobes.emplace_back (in, max_per_voxel, dir_from_peak); + lobes.emplace_back(in, max_per_voxel, dir_from_peak); fixel_count += lobes.back().size(); } return true; } - - -void Segmented_FOD_receiver::commit () -{ +void Segmented_FOD_receiver::commit() { if (!lobes.size() || !fixel_count) return; using DataImage = Image; using IndexImage = Image; - const auto index_filepath = Path::join (fixel_directory_path, index_path); + const auto index_filepath = Path::join(fixel_directory_path, index_path); std::unique_ptr index_image; std::unique_ptr dir_image; @@ -191,15 +171,15 @@ void Segmented_FOD_receiver::commit () std::unique_ptr peak_amp_image; std::unique_ptr disp_image; - auto index_header (H); + auto index_header(H); index_header.keyval()[Fixel::n_fixels_key] = str(fixel_count); index_header.ndim() = 4; index_header.size(3) = 2; index_header.datatype() = DataType::from(); index_header.datatype().set_byte_order_native(); - index_image = make_unique (IndexImage::create (index_filepath, index_header)); + index_image = make_unique(IndexImage::create(index_filepath, index_header)); - auto fixel_data_header (H); + auto fixel_data_header(H); fixel_data_header.ndim() = 3; fixel_data_header.size(0) = fixel_count; fixel_data_header.size(2) = 1; @@ -209,47 +189,47 @@ void Segmented_FOD_receiver::commit () fixel_data_header.datatype().set_byte_order_native(); if (dir_path.size()) { - auto dir_header (fixel_data_header); + auto dir_header(fixel_data_header); dir_header.size(1) = 3; - dir_image = make_unique (DataImage::create (Path::join(fixel_directory_path, dir_path), dir_header)); + dir_image = make_unique(DataImage::create(Path::join(fixel_directory_path, dir_path), dir_header)); dir_image->index(1) = 0; - Fixel::check_fixel_size (*index_image, *dir_image); + Fixel::check_fixel_size(*index_image, *dir_image); } if (afd_path.size()) { - auto afd_header (fixel_data_header); + auto afd_header(fixel_data_header); afd_header.size(1) = 1; - afd_image = make_unique (DataImage::create (Path::join(fixel_directory_path, afd_path), afd_header)); + afd_image = make_unique(DataImage::create(Path::join(fixel_directory_path, afd_path), afd_header)); afd_image->index(1) = 0; - Fixel::check_fixel_size (*index_image, *afd_image); + Fixel::check_fixel_size(*index_image, *afd_image); } if (peak_amp_path.size()) { - auto peak_amp_header (fixel_data_header); + auto peak_amp_header(fixel_data_header); peak_amp_header.size(1) = 1; - peak_amp_image = make_unique (DataImage::create (Path::join(fixel_directory_path, peak_amp_path), peak_amp_header)); + peak_amp_image = + make_unique(DataImage::create(Path::join(fixel_directory_path, peak_amp_path), peak_amp_header)); peak_amp_image->index(1) = 0; - Fixel::check_fixel_size (*index_image, *peak_amp_image); + Fixel::check_fixel_size(*index_image, *peak_amp_image); } if (disp_path.size()) { - auto disp_header (fixel_data_header); + auto disp_header(fixel_data_header); disp_header.size(1) = 1; - disp_image = make_unique (DataImage::create (Path::join(fixel_directory_path, disp_path), disp_header)); + disp_image = make_unique(DataImage::create(Path::join(fixel_directory_path, disp_path), disp_header)); disp_image->index(1) = 0; - Fixel::check_fixel_size (*index_image, *disp_image); + Fixel::check_fixel_size(*index_image, *disp_image); } size_t offset = 0; - - for (const auto& vox_fixels : lobes) { + for (const auto &vox_fixels : lobes) { size_t n_vox_fixels = vox_fixels.size(); - assign_pos_of (vox_fixels.vox).to (*index_image); + assign_pos_of(vox_fixels.vox).to(*index_image); index_image->index(3) = 0; - index_image->value () = n_vox_fixels; + index_image->value() = n_vox_fixels; index_image->index(3) = 1; index_image->value() = offset; @@ -285,56 +265,57 @@ void Segmented_FOD_receiver::commit () offset += n_vox_fixels; } - assert (offset == fixel_count); + assert(offset == fixel_count); } - - - -void run () -{ - Header H = Header::open (argument[0]); - Math::SH::check (H); +void run() { + Header H = Header::open(argument[0]); + Math::SH::check(H); auto fod_data = H.get_image(); - const bool dir_as_peak = get_options ("dirpeak").size(); - const index_type maxnum = get_option_value ("maxnum", 0); + const bool dir_as_peak = get_options("dirpeak").size(); + const index_type maxnum = get_option_value("maxnum", 0); - Segmented_FOD_receiver receiver (H, maxnum, dir_as_peak); + Segmented_FOD_receiver receiver(H, maxnum, dir_as_peak); - auto& fixel_directory_path = argument[1]; - receiver.set_fixel_directory_output (fixel_directory_path); + auto &fixel_directory_path = argument[1]; + receiver.set_fixel_directory_output(fixel_directory_path); - std::string file_extension (".mif"); - if (get_options ("nii").size()) + std::string file_extension(".mif"); + if (get_options("nii").size()) file_extension = ".nii"; - static const std::string default_index_filename ("index" + file_extension); - static const std::string default_directions_filename ("directions" + file_extension); - receiver.set_index_output (default_index_filename); - receiver.set_directions_output (default_directions_filename); - - auto - opt = get_options ("afd"); if (opt.size()) receiver.set_afd_output (opt[0][0]); - opt = get_options ("peak_amp"); if (opt.size()) receiver.set_peak_amp_output (opt[0][0]); - opt = get_options ("disp"); if (opt.size()) receiver.set_disp_output (opt[0][0]); - - opt = get_options ("mask"); + static const std::string default_index_filename("index" + file_extension); + static const std::string default_directions_filename("directions" + file_extension); + receiver.set_index_output(default_index_filename); + receiver.set_directions_output(default_directions_filename); + + auto opt = get_options("afd"); + if (opt.size()) + receiver.set_afd_output(opt[0][0]); + opt = get_options("peak_amp"); + if (opt.size()) + receiver.set_peak_amp_output(opt[0][0]); + opt = get_options("disp"); + if (opt.size()) + receiver.set_disp_output(opt[0][0]); + + opt = get_options("mask"); Image mask; if (opt.size()) { - mask = Image::open (std::string (opt[0][0])); - if (!dimensions_match (fod_data, mask, 0, 3)) - throw Exception ("Cannot use image \"" + str(opt[0][0]) + "\" as mask image; dimensions do not match FOD image"); + mask = Image::open(std::string(opt[0][0])); + if (!dimensions_match(fod_data, mask, 0, 3)) + throw Exception("Cannot use image \"" + str(opt[0][0]) + "\" as mask image; dimensions do not match FOD image"); } - Fixel::check_fixel_directory (fixel_directory_path, true, true); + Fixel::check_fixel_directory(fixel_directory_path, true, true); - FMLS::FODQueueWriter writer (fod_data, mask); + FMLS::FODQueueWriter writer(fod_data, mask); - const DWI::Directions::FastLookupSet dirs (1281); - Segmenter fmls (dirs, Math::SH::LforN (H.size(3))); - load_fmls_thresholds (fmls); + const DWI::Directions::FastLookupSet dirs(1281); + Segmenter fmls(dirs, Math::SH::LforN(H.size(3))); + load_fmls_thresholds(fmls); - Thread::run_queue (writer, Thread::batch (SH_coefs()), Thread::multi (fmls), Thread::batch (FOD_lobes()), receiver); - receiver.commit (); + Thread::run_queue(writer, Thread::batch(SH_coefs()), Thread::multi(fmls), Thread::batch(FOD_lobes()), receiver); + receiver.commit(); } diff --git a/cmd/label2colour.cpp b/cmd/label2colour.cpp index bfe0364abe..8181ac203c 100644 --- a/cmd/label2colour.cpp +++ b/cmd/label2colour.cpp @@ -26,99 +26,92 @@ #include "connectome/connectome.h" #include "connectome/lut.h" - - using namespace MR; using namespace App; using namespace MR::Connectome; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Convert a parcellated image (where values are node indices) into a colour image"; DESCRIPTION - + "Many software packages handle this colouring internally within their viewer program; this binary " - "explicitly converts a parcellation image into a colour image that should be viewable in any software."; + +"Many software packages handle this colouring internally within their viewer program; this binary " + "explicitly converts a parcellation image into a colour image that should be viewable in any software."; ARGUMENTS - + Argument ("nodes_in", "the input node parcellation image").type_image_in() - + Argument ("colour_out", "the output colour image").type_image_out(); + +Argument("nodes_in", "the input node parcellation image").type_image_in() + + Argument("colour_out", "the output colour image").type_image_out(); OPTIONS - + Option ("lut", "Provide the relevant colour lookup table " - "(if not provided, nodes will be coloured randomly)") - + Argument ("file").type_file_in(); - + +Option("lut", + "Provide the relevant colour lookup table " + "(if not provided, nodes will be coloured randomly)") + + Argument("file").type_file_in(); } +void run() { - - - -void run () -{ - - auto H = Header::open (argument[0]); - Connectome::check (H); + auto H = Header::open(argument[0]); + Connectome::check(H); auto nodes = H.get_image(); - const std::string lut_path = get_option_value ("lut", ""); + const std::string lut_path = get_option_value("lut", ""); LUT lut; if (lut_path.size()) { - lut.load (lut_path); + lut.load(lut_path); } else { - INFO ("No lookup table provided; colouring nodes randomly"); + INFO("No lookup table provided; colouring nodes randomly"); node_t max_index = 0; - for (auto l = Loop (nodes) (nodes); l; ++l) { + for (auto l = Loop(nodes)(nodes); l; ++l) { const node_t index = nodes.value(); if (index > max_index) max_index = index; } - lut.insert (std::make_pair (0, LUT_node ("None", 0, 0, 0, 0))); + lut.insert(std::make_pair(0, LUT_node("None", 0, 0, 0, 0))); Math::RNG rng; std::uniform_int_distribution dist; for (node_t i = 1; i <= max_index; ++i) { LUT_node::RGB colour; do { - colour[0] = dist (rng); - colour[1] = dist (rng); - colour[2] = dist (rng); + colour[0] = dist(rng); + colour[1] = dist(rng); + colour[2] = dist(rng); } while (int(colour[0]) + int(colour[1]) + int(colour[2]) < 100); - lut.insert (std::make_pair (i, LUT_node (str(i), colour))); + lut.insert(std::make_pair(i, LUT_node(str(i), colour))); } } H.ndim() = 4; - H.size (3) = 3; + H.size(3) = 3; H.datatype() = DataType::UInt8; - add_line (H.keyval()["comments"], "Coloured parcellation image generated by label2colour"); + add_line(H.keyval()["comments"], "Coloured parcellation image generated by label2colour"); if (lut_path.size()) - H.keyval()["LUT"] = Path::basename (lut_path); - auto out = Image::create (argument[1], H); + H.keyval()["LUT"] = Path::basename(lut_path); + auto out = Image::create(argument[1], H); - for (auto l = Loop ("Colourizing parcellated node image", nodes) (nodes, out); l; ++l) { + for (auto l = Loop("Colourizing parcellated node image", nodes)(nodes, out); l; ++l) { const node_t index = nodes.value(); - const LUT::const_iterator i = lut.find (index); + const LUT::const_iterator i = lut.find(index); if (i == lut.end()) { - out.index (3) = 0; out.value() = 0; - out.index (3) = 1; out.value() = 0; - out.index (3) = 2; out.value() = 0; + out.index(3) = 0; + out.value() = 0; + out.index(3) = 1; + out.value() = 0; + out.index(3) = 2; + out.value() = 0; } else { - const LUT_node::RGB& colour (i->second.get_colour()); - out.index (3) = 0; out.value() = colour[0]; - out.index (3) = 1; out.value() = colour[1]; - out.index (3) = 2; out.value() = colour[2]; + const LUT_node::RGB &colour(i->second.get_colour()); + out.index(3) = 0; + out.value() = colour[0]; + out.index(3) = 1; + out.value() = colour[1]; + out.index(3) = 2; + out.value() = colour[2]; } } - } - - - diff --git a/cmd/label2mesh.cpp b/cmd/label2mesh.cpp index cb57a06b62..b8bbf38447 100644 --- a/cmd/label2mesh.cpp +++ b/cmd/label2mesh.cpp @@ -22,123 +22,114 @@ #include "thread_queue.h" #include "types.h" -#include "algo/loop.h" #include "adapter/subset.h" +#include "algo/loop.h" #include "connectome/connectome.h" +#include "surface/algo/image2mesh.h" #include "surface/mesh.h" #include "surface/mesh_multi.h" -#include "surface/algo/image2mesh.h" - using namespace MR; using namespace App; using namespace MR::Surface; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Generate meshes from a label image"; ARGUMENTS - + Argument ("nodes_in", "the input node parcellation image").type_image_in() - + Argument ("mesh_out", "the output mesh file").type_file_out(); + +Argument("nodes_in", "the input node parcellation image").type_image_in() + + Argument("mesh_out", "the output mesh file").type_file_out(); OPTIONS - + Option ("blocky", "generate 'blocky' meshes with precise delineation of voxel edges, " - "rather than the default Marching Cubes approach"); - + +Option("blocky", + "generate 'blocky' meshes with precise delineation of voxel edges, " + "rather than the default Marching Cubes approach"); } +void run() { - - - -void run () -{ - - Header labels_header = Header::open (argument[0]); - Connectome::check (labels_header); - check_3D_nonunity (labels_header); + Header labels_header = Header::open(argument[0]); + Connectome::check(labels_header); + check_3D_nonunity(labels_header); auto labels = labels_header.get_image(); using voxel_corner_t = Eigen::Array; vector lower_corners, upper_corners; { - for (auto i = Loop ("Importing label image", labels) (labels); i; ++i) { + for (auto i = Loop("Importing label image", labels)(labels); i; ++i) { const uint32_t index = labels.value(); if (index) { if (index >= lower_corners.size()) { - lower_corners.resize (index+1, voxel_corner_t (labels.size(0), labels.size(1), labels.size(2))); - upper_corners.resize (index+1, voxel_corner_t (-1, -1, -1)); + lower_corners.resize(index + 1, voxel_corner_t(labels.size(0), labels.size(1), labels.size(2))); + upper_corners.resize(index + 1, voxel_corner_t(-1, -1, -1)); } for (size_t axis = 0; axis != 3; ++axis) { - lower_corners[index][axis] = std::min (lower_corners[index][axis], int(labels.index (axis))); - upper_corners[index][axis] = std::max (upper_corners[index][axis], int(labels.index (axis))); + lower_corners[index][axis] = std::min(lower_corners[index][axis], int(labels.index(axis))); + upper_corners[index][axis] = std::max(upper_corners[index][axis], int(labels.index(axis))); } - } } } - MeshMulti meshes (lower_corners.size(), MR::Surface::Mesh()); - meshes[0].set_name ("none"); - const bool blocky = get_options ("blocky").size(); + MeshMulti meshes(lower_corners.size(), MR::Surface::Mesh()); + meshes[0].set_name("none"); + const bool blocky = get_options("blocky").size(); vector missing_nodes; for (uint32_t i = 1; i != upper_corners.size(); ++i) { if (upper_corners[i][0] < 0) - missing_nodes.push_back (i); + missing_nodes.push_back(i); } if (missing_nodes.size()) { - WARN ("The following labels are absent from the parcellation image " - "and so will have an empty mesh in the output file: " - + join(missing_nodes, ", ")); + WARN("The following labels are absent from the parcellation image " + "and so will have an empty mesh in the output file: " + + join(missing_nodes, ", ")); } { std::mutex mutex; - ProgressBar progress ("Generating meshes from labels", lower_corners.size() - 1); - auto loader = [&] (size_t& out) { static size_t i = 1; out = i++; return (out != lower_corners.size()); }; + ProgressBar progress("Generating meshes from labels", lower_corners.size() - 1); + auto loader = [&](size_t &out) { + static size_t i = 1; + out = i++; + return (out != lower_corners.size()); + }; - auto worker = [&] (const size_t& in) - { - meshes[in].set_name (str(in)); + auto worker = [&](const size_t &in) { + meshes[in].set_name(str(in)); vector from, dimensions; for (size_t axis = 0; axis != 3; ++axis) { - from.push_back (lower_corners[in][axis]); - dimensions.push_back (upper_corners[in][axis] - lower_corners[in][axis] + 1); + from.push_back(lower_corners[in][axis]); + dimensions.push_back(upper_corners[in][axis] - lower_corners[in][axis] + 1); if (dimensions.back() < 1) return true; } - Adapter::Subset> subset (labels, from, dimensions); + Adapter::Subset> subset(labels, from, dimensions); - auto scratch = Image::scratch (subset, "Node " + str(in) + " mask"); - for (auto i = Loop (subset) (subset, scratch); i; ++i) + auto scratch = Image::scratch(subset, "Node " + str(in) + " mask"); + for (auto i = Loop(subset)(subset, scratch); i; ++i) scratch.value() = (subset.value() == in); if (blocky) - MR::Surface::Algo::image2mesh_blocky (scratch, meshes[in]); + MR::Surface::Algo::image2mesh_blocky(scratch, meshes[in]); else - MR::Surface::Algo::image2mesh_mc (scratch, meshes[in], 0.5); - std::lock_guard lock (mutex); + MR::Surface::Algo::image2mesh_mc(scratch, meshes[in], 0.5); + std::lock_guard lock(mutex); ++progress; return true; }; - Thread::run_queue (loader, size_t(), Thread::multi (worker)); + Thread::run_queue(loader, size_t(), Thread::multi(worker)); } - meshes.save (argument[1]); + meshes.save(argument[1]); } - - - diff --git a/cmd/labelconvert.cpp b/cmd/labelconvert.cpp index f0332a5b79..bc078ab595 100644 --- a/cmd/labelconvert.cpp +++ b/cmd/labelconvert.cpp @@ -31,85 +31,76 @@ #include - #define SPINE_NODE_NAME std::string("Spinal_column") - - - using namespace MR; using namespace App; using namespace MR::Connectome; using MR::Connectome::node_t; - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Convert a connectome node image from one lookup table to another"; DESCRIPTION - + "Typical usage is to convert a parcellation image provided by some other software, based on " - "the lookup table provided by that software, to conform to a new lookup table, particularly " - "one where the node indices increment from 1, in preparation for connectome construction; " - "examples of such target lookup table files are provided in share//mrtrix3//labelconvert//, " - "but can be created by the user to provide the desired node set // ordering // colours." + +"Typical usage is to convert a parcellation image provided by some other software, based on " + "the lookup table provided by that software, to conform to a new lookup table, particularly " + "one where the node indices increment from 1, in preparation for connectome construction; " + "examples of such target lookup table files are provided in share//mrtrix3//labelconvert//, " + "but can be created by the user to provide the desired node set // ordering // colours." - + "A more thorough description of the operation and purpose of the labelconvert command " - "can be found in the online documentation: \n" - "https://mrtrix.readthedocs.io/en/" MRTRIX_BASE_VERSION "/quantitative_structural_connectivity/labelconvert_tutorial.html"; + + "A more thorough description of the operation and purpose of the labelconvert command " + "can be found in the online documentation: \n" + "https://mrtrix.readthedocs.io/en/" MRTRIX_BASE_VERSION + "/quantitative_structural_connectivity/labelconvert_tutorial.html"; EXAMPLES - + Example ("Convert a Desikan-Killiany parcellation image as provided by FreeSurfer to have nodes incrementing from 1", - "labelconvert aparc+aseg.mgz FreeSurferColorLUT.txt mrtrix3//share//mrtrix3//labelconvert//fs_default.txt nodes.mif", - "Paths to the files in the example above would need to be revised according to their " - "locations on the user's system."); + +Example("Convert a Desikan-Killiany parcellation image as provided by FreeSurfer to have nodes incrementing from 1", + "labelconvert aparc+aseg.mgz FreeSurferColorLUT.txt mrtrix3//share//mrtrix3//labelconvert//fs_default.txt " + "nodes.mif", + "Paths to the files in the example above would need to be revised according to their " + "locations on the user's system."); ARGUMENTS - + Argument ("path_in", "the input image").type_image_in() - + Argument ("lut_in", "the connectome lookup table corresponding to the input image").type_file_in() - + Argument ("lut_out", "the target connectome lookup table for the output image").type_file_in() - + Argument ("image_out", "the output image").type_image_out(); - + +Argument("path_in", "the input image").type_image_in() + + Argument("lut_in", "the connectome lookup table corresponding to the input image").type_file_in() + + Argument("lut_out", "the target connectome lookup table for the output image").type_file_in() + + Argument("image_out", "the output image").type_image_out(); OPTIONS - + Option ("spine", "provide a manually-defined segmentation of the base of the spine where the streamlines terminate, so that " - "this can become a node in the connection matrix.") - + Argument ("image").type_image_in(); - + +Option("spine", + "provide a manually-defined segmentation of the base of the spine where the streamlines terminate, so that " + "this can become a node in the connection matrix.") + + Argument("image").type_image_in(); } - - -void run () -{ +void run() { // Open the input file - auto H = Header::open (argument[0]); - Connectome::check (H); + auto H = Header::open(argument[0]); + Connectome::check(H); auto in = H.get_image(); // Load the lookup tables - LUT lut_in (argument[1]), lut_out (argument[2]); + LUT lut_in(argument[1]), lut_out(argument[2]); // Build the mapping from input to output indices - const auto mapping = get_lut_mapping (lut_in, lut_out); - if (*std::max_element (mapping.begin(), mapping.end()) == 0) - throw Exception ("Mapping between input and output LUTs is empty, i.e. no common node names between these two LUTs"); + const auto mapping = get_lut_mapping(lut_in, lut_out); + if (*std::max_element(mapping.begin(), mapping.end()) == 0) + throw Exception("Mapping between input and output LUTs is empty, i.e. no common node names between these two LUTs"); // Modify the header for the output file H.datatype() = DataType::from(); - add_line (H.keyval()["comments"], "LUT: " + Path::basename (argument[2])); + add_line(H.keyval()["comments"], "LUT: " + Path::basename(argument[2])); // Create the output file - auto out = Image::create (argument[3], H); + auto out = Image::create(argument[3], H); // Fill the output image with data bool user_warn = false; - for (auto l = Loop (in) (in, out); l; ++l) { + for (auto l = Loop(in)(in, out); l; ++l) { const node_t orig = in.value(); if (orig < mapping.size()) out.value() = mapping[orig]; @@ -117,13 +108,13 @@ void run () user_warn = true; } if (user_warn) - WARN ("Unexpected values detected in input image; suggest checking input image thoroughly"); + WARN("Unexpected values detected in input image; suggest checking input image thoroughly"); // Need to manually search through the output LUT to see if the // 'Spinal_column' node is in there, and appears only once node_t spine_index = 0; bool duplicates = false; - for (const auto& i : lut_out) { + for (const auto &i : lut_out) { if (i.second.get_name() == SPINE_NODE_NAME) { if (!spine_index) spine_index = i.first; @@ -133,41 +124,40 @@ void run () } // If the spine segment option has been provided, add this retrospectively - auto opt = get_options ("spine"); + auto opt = get_options("spine"); if (opt.size()) { if (duplicates) - throw Exception ("Cannot add spine node: \"" + SPINE_NODE_NAME + "\" appears multiple times in output LUT"); + throw Exception("Cannot add spine node: \"" + SPINE_NODE_NAME + "\" appears multiple times in output LUT"); if (!spine_index) - throw Exception ("Cannot add spine node: \"" + SPINE_NODE_NAME + "\" not present in output LUT"); + throw Exception("Cannot add spine node: \"" + SPINE_NODE_NAME + "\" not present in output LUT"); - auto in_spine = Image::open (opt[0][0]); - if (dimensions_match (in_spine, out)) { + auto in_spine = Image::open(opt[0][0]); + if (dimensions_match(in_spine, out)) { - for (auto l = Loop (in_spine) (in_spine, out); l; ++l) { + for (auto l = Loop(in_spine)(in_spine, out); l; ++l) { if (in_spine.value()) out.value() = spine_index; } } else { - WARN ("Spine node is being created from the mask image provided using -spine option using nearest-neighbour interpolation;"); - WARN ("recommend using the parcellation image as the basis for this mask so that interpolation is not required"); + WARN("Spine node is being created from the mask image provided using -spine option using nearest-neighbour " + "interpolation;"); + WARN("recommend using the parcellation image as the basis for this mask so that interpolation is not required"); - Transform transform (out); - Interp::Nearest nearest (in_spine); - for (auto l = Loop (out) (out); l; ++l) { - Eigen::Vector3d p (out.index (0), out.index (1), out.index (2)); + Transform transform(out); + Interp::Nearest nearest(in_spine); + for (auto l = Loop(out)(out); l; ++l) { + Eigen::Vector3d p(out.index(0), out.index(1), out.index(2)); p = transform.voxel2scanner * p; - if (nearest.scanner (p) && nearest.value()) + if (nearest.scanner(p) && nearest.value()) out.value() = spine_index; } - } } else if (spine_index) { - WARN ("Config file includes \"" + SPINE_NODE_NAME + "\" node, but user has not provided the segmentation using -spine option"); + WARN("Config file includes \"" + SPINE_NODE_NAME + + "\" node, but user has not provided the segmentation using -spine option"); } - } - diff --git a/cmd/labelstats.cpp b/cmd/labelstats.cpp index 728f9e5550..5c1c8988f9 100644 --- a/cmd/labelstats.cpp +++ b/cmd/labelstats.cpp @@ -14,7 +14,6 @@ * For more details, see http://www.mrtrix.org/. */ - #include "command.h" #include "header.h" #include "image.h" @@ -27,55 +26,49 @@ using namespace MR; using namespace App; +const char *field_choices[] = {"mass", "centre", nullptr}; -const char * field_choices[] = { "mass", "centre", nullptr }; - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Compute statistics of parcels within a label image"; ARGUMENTS - + Argument ("input", "the input label image").type_image_in(); + +Argument("input", "the input label image").type_image_in(); OPTIONS - + Option ("output", "output only the field specified; " - "options are: " + join(field_choices, ",")) - + Argument ("choice").type_choice (field_choices) - - + Option ("voxelspace", "report parcel centres of mass in voxel space rather than scanner space"); + +Option("output", + "output only the field specified; " + "options are: " + + join(field_choices, ",")) + + Argument("choice").type_choice(field_choices) + + Option("voxelspace", "report parcel centres of mass in voxel space rather than scanner space"); } - using Connectome::node_t; using vector_type = Eigen::Array; using matrix_type = Eigen::Matrix; - - -void run () -{ - Header H = Header::open (argument[0]); +void run() { + Header H = Header::open(argument[0]); if (H.ndim() > 3) - throw Exception ("Command does not accept images with more than 3 dimensions"); - Connectome::check (H); + throw Exception("Command does not accept images with more than 3 dimensions"); + Connectome::check(H); Image image = H.get_image(); matrix_type coms; vector_type masses; - for (auto l = Loop(image) (image); l; ++l) { + for (auto l = Loop(image)(image); l; ++l) { const node_t value = image.value(); if (value) { if (value > coms.rows()) { - coms.conservativeResizeLike (matrix_type::Zero (value, 3)); - masses.conservativeResizeLike (vector_type::Zero (value)); + coms.conservativeResizeLike(matrix_type::Zero(value, 3)); + masses.conservativeResizeLike(vector_type::Zero(value)); } - coms.row(value-1) += Eigen::Vector3d (image.index(0), image.index(1), image.index(2)); - masses[value-1]++; + coms.row(value - 1) += Eigen::Vector3d(image.index(0), image.index(1), image.index(2)); + masses[value - 1]++; } } @@ -84,41 +77,46 @@ void run () if (!get_options("voxelspace").size()) coms = (image.transform() * coms.transpose()).transpose(); - auto opt = get_options ("output"); + auto opt = get_options("output"); if (opt.size()) { switch (int(opt[0][0])) { - case 0: std::cout << masses; break; - case 1: std::cout << coms; break; - default: assert (0); + case 0: + std::cout << masses; + break; + case 1: + std::cout << coms; + break; + default: + assert(0); } return; } // Ensure that the printout of centres-of-mass is nicely formatted - Eigen::IOFormat fmt (Eigen::StreamPrecision, 0, ", ", "\n", "[ ", " ]", "", ""); + Eigen::IOFormat fmt(Eigen::StreamPrecision, 0, ", ", "\n", "[ ", " ]", "", ""); std::stringstream com_stringstream; - com_stringstream << coms.format (fmt); + com_stringstream << coms.format(fmt); const vector com_strings = split(com_stringstream.str(), "\n"); - assert (com_strings.size() == size_t(masses.size())); + assert(com_strings.size() == size_t(masses.size())); // Find width of first non-empty string, in order to centralise header label size_t com_width = 0; - for (const auto& i : com_strings) { + for (const auto &i : com_strings) { if (i.size()) { com_width = i.size(); break; } } - std::cout << std::setw(8) << std::right << "index" << " " - << std::setw(8) << std::right << "mass" << " " - << std::setw(4) << std::right << " " - << std::setw(com_width/2 + std::string("centre of mass").size()/2) << std::right << "centre of mass" << "\n"; + std::cout << std::setw(8) << std::right << "index" + << " " << std::setw(8) << std::right << "mass" + << " " << std::setw(4) << std::right << " " + << std::setw(com_width / 2 + std::string("centre of mass").size() / 2) << std::right << "centre of mass" + << "\n"; for (ssize_t i = 0; i != masses.size(); ++i) { if (masses[i]) { - std::cout << std::setw(8) << std::right << i+1 << " " - << std::setw(8) << std::right << masses[i] << " " - << std::setw(4+com_width) << std::right << com_strings[i] << "\n"; + std::cout << std::setw(8) << std::right << i + 1 << " " << std::setw(8) << std::right << masses[i] << " " + << std::setw(4 + com_width) << std::right << com_strings[i] << "\n"; } } } diff --git a/cmd/maskdump.cpp b/cmd/maskdump.cpp index 8d847c91b3..0677eee5b8 100644 --- a/cmd/maskdump.cpp +++ b/cmd/maskdump.cpp @@ -14,52 +14,48 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "image.h" #include "algo/loop.h" +#include "command.h" #include "file/matrix.h" +#include "image.h" using namespace MR; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Print out the locations of all non-zero voxels in a mask image"; DESCRIPTION - + "If no destination file is specified, the voxel locations will be " - "printed to stdout."; + +"If no destination file is specified, the voxel locations will be " + "printed to stdout."; ARGUMENTS - + Argument ("input", "the input image.").type_image_in() - + Argument ("output", "the (optional) output text file.").type_file_out().optional(); + +Argument("input", "the input image.").type_image_in() + + Argument("output", "the (optional) output text file.").type_file_out().optional(); } - -void run () -{ - auto H = Header::open (argument[0]); +void run() { + auto H = Header::open(argument[0]); if (H.datatype() != DataType::Bit) - WARN ("Input is not a genuine boolean mask image"); + WARN("Input is not a genuine boolean mask image"); auto in = H.get_image(); - vector< Eigen::ArrayXi > locations; - for (auto l = Loop(in) (in); l; ++l) { + vector locations; + for (auto l = Loop(in)(in); l; ++l) { if (in.value()) { - Eigen::ArrayXi this_voxel (in.ndim()); + Eigen::ArrayXi this_voxel(in.ndim()); for (size_t axis = 0; axis != in.ndim(); ++axis) - this_voxel[axis] = in.index (axis); - locations.push_back (std::move (this_voxel)); + this_voxel[axis] = in.index(axis); + locations.push_back(std::move(this_voxel)); } } - Eigen::ArrayXXi prettyprint (locations.size(), in.ndim()); + Eigen::ArrayXXi prettyprint(locations.size(), in.ndim()); for (size_t row = 0; row != locations.size(); ++row) - prettyprint.row (row) = std::move (locations[row]); - INFO ("Printing locations of " + str(prettyprint.rows()) + " non-zero voxels"); + prettyprint.row(row) = std::move(locations[row]); + INFO("Printing locations of " + str(prettyprint.rows()) + " non-zero voxels"); if (argument.size() == 2) - File::Matrix::save_matrix (prettyprint, argument[1]); + File::Matrix::save_matrix(prettyprint, argument[1]); else std::cout << prettyprint; } diff --git a/cmd/maskfilter.cpp b/cmd/maskfilter.cpp index 176fff0c23..768a8b62b2 100644 --- a/cmd/maskfilter.cpp +++ b/cmd/maskfilter.cpp @@ -15,7 +15,6 @@ */ #include "command.h" -#include "image.h" #include "filter/base.h" #include "filter/connected_components.h" #include "filter/dilate.h" @@ -23,204 +22,195 @@ #include "filter/fill.h" #include "filter/mask_clean.h" #include "filter/median.h" +#include "image.h" using namespace MR; using namespace App; #define DEFAULT_CLEAN_SCALE 2 +const char *filters[] = {"clean", "connect", "dilate", "erode", "fill", "median", nullptr}; -const char* filters[] = { "clean", "connect", "dilate", "erode", "fill", "median", nullptr }; - - - -const OptionGroup CleanOption = OptionGroup ("Options for mask cleaning filter") - - + Option ("scale", "the maximum scale used to cut bridges. A certain maximum scale cuts " - "bridges up to a width (in voxels) of 2x the provided scale. (Default: " + str(DEFAULT_CLEAN_SCALE, 2) + ")") - + Argument ("value").type_integer (1, 1e6); - - - -const OptionGroup ConnectOption = OptionGroup ("Options for connected-component filter") - -+ Option ("axes", "specify which axes should be included in the connected components. By default only " - "the first 3 axes are included. The axes should be provided as a comma-separated list of values.") - + Argument ("axes").type_sequence_int() +const OptionGroup CleanOption = OptionGroup("Options for mask cleaning filter") -+ Option ("largest", "only retain the largest connected component") + + Option("scale", + "the maximum scale used to cut bridges. A certain maximum scale cuts " + "bridges up to a width (in voxels) of 2x the provided scale. (Default: " + + str(DEFAULT_CLEAN_SCALE, 2) + ")") + + Argument("value").type_integer(1, 1e6); -+ Option ("connectivity", "use 26-voxel-neighbourhood connectivity (Default: 6)") +const OptionGroup ConnectOption = + OptionGroup("Options for connected-component filter") -+ Option ("minsize", "impose minimum size of segmented components (Default: select all components)") - + Argument ("value").type_integer (1, 1e6); + + Option("axes", + "specify which axes should be included in the connected components. By default only " + "the first 3 axes are included. The axes should be provided as a comma-separated list of values.") + + Argument("axes").type_sequence_int() + + Option("largest", "only retain the largest connected component") + + Option("connectivity", "use 26-voxel-neighbourhood connectivity (Default: 6)") -const OptionGroup DilateErodeOption = OptionGroup ("Options for dilate / erode filters") + + Option("minsize", "impose minimum size of segmented components (Default: select all components)") + + Argument("value").type_integer(1, 1e6); - + Option ("npass", "the number of times to repeatedly apply the filter") - + Argument ("value").type_integer (1, 1e6); +const OptionGroup DilateErodeOption = OptionGroup("Options for dilate / erode filters") + + Option("npass", "the number of times to repeatedly apply the filter") + + Argument("value").type_integer(1, 1e6); +const OptionGroup FillOption = + OptionGroup("Options for interior-filling filter") -const OptionGroup FillOption = OptionGroup ("Options for interior-filling filter") + + Option("axes", + "specify which axes should be included in the connected components. By default only " + "the first 3 axes are included. The axes should be provided as a comma-separated list of values.") + + Argument("axes").type_sequence_int() -+ Option ("axes", "specify which axes should be included in the connected components. By default only " - "the first 3 axes are included. The axes should be provided as a comma-separated list of values.") - + Argument ("axes").type_sequence_int() + + Option("connectivity", "use 26-voxel-neighbourhood connectivity (Default: 6)"); -+ Option ("connectivity", "use 26-voxel-neighbourhood connectivity (Default: 6)"); +const OptionGroup MedianOption = + OptionGroup("Options for median filter") + + Option("extent", + "specify the extent (width) of kernel size in voxels. " + "This can be specified either as a single value to be used for all axes, " + "or as a comma-separated list of the extent for each axis. The default is 3x3x3.") + + Argument("voxels").type_sequence_int(); -const OptionGroup MedianOption = OptionGroup ("Options for median filter") - - + Option ("extent", "specify the extent (width) of kernel size in voxels. " - "This can be specified either as a single value to be used for all axes, " - "or as a comma-separated list of the extent for each axis. The default is 3x3x3.") - + Argument ("voxels").type_sequence_int(); - - - - - -void usage () -{ - AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au), David Raffelt (david.raffelt@florey.edu.au), Thijs Dhollander (thijs.dhollander@gmail.com) and J-Donald Tournier (jdtournier@gmail.com)"; +void usage() { + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au), David Raffelt (david.raffelt@florey.edu.au), Thijs " + "Dhollander (thijs.dhollander@gmail.com) and J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Perform filtering operations on 3D / 4D mask images"; DESCRIPTION - + "Many filters have their own unique set of optional parameters; " - "see the option groups dedicated to each filter type."; + +"Many filters have their own unique set of optional parameters; " + "see the option groups dedicated to each filter type."; ARGUMENTS - + Argument ("input", "the input mask.").type_image_in () - + Argument ("filter", "the name of the filter to be applied; options are: " + join(filters, ", ")).type_choice (filters) - + Argument ("output", "the output mask.").type_image_out (); - + +Argument("input", "the input mask.").type_image_in() + + Argument("filter", "the name of the filter to be applied; options are: " + join(filters, ", ")) + .type_choice(filters) + + Argument("output", "the output mask.").type_image_out(); OPTIONS - + CleanOption - + ConnectOption - + DilateErodeOption - + FillOption - + MedianOption + +CleanOption + ConnectOption + DilateErodeOption + FillOption + MedianOption - + Stride::Options; + + Stride::Options; } - using value_type = bool; -void run () { +void run() { - auto input_image = Image::open (argument[0]); + auto input_image = Image::open(argument[0]); int filter_index = argument[1]; if (filter_index == 0) { // Mask clean - Filter::MaskClean filter (input_image, std::string("applying mask cleaning filter to image ") + Path::basename (argument[0])); - filter.set_scale(get_option_value ("scale", DEFAULT_CLEAN_SCALE)); + Filter::MaskClean filter(input_image, + std::string("applying mask cleaning filter to image ") + Path::basename(argument[0])); + filter.set_scale(get_option_value("scale", DEFAULT_CLEAN_SCALE)); - Stride::set_from_command_line (filter); + Stride::set_from_command_line(filter); - auto output_image = Image::create (argument[2], filter); - filter (input_image, output_image); + auto output_image = Image::create(argument[2], filter); + filter(input_image, output_image); return; } if (filter_index == 1) { // Connected components - Filter::ConnectedComponents filter (input_image, std::string("applying connected-component filter to image ") + Path::basename (argument[0])); - auto opt = get_options ("axes"); + Filter::ConnectedComponents filter( + input_image, std::string("applying connected-component filter to image ") + Path::basename(argument[0])); + auto opt = get_options("axes"); if (opt.size()) { const vector axes = opt[0][0]; - filter.set_axes (axes); + filter.set_axes(axes); } bool largest_only = false; - opt = get_options ("largest"); + opt = get_options("largest"); if (opt.size()) { largest_only = true; - filter.set_largest_only (true); + filter.set_largest_only(true); } - opt = get_options ("connectivity"); + opt = get_options("connectivity"); if (opt.size()) - filter.set_26_connectivity (true); - opt = get_options ("minsize"); + filter.set_26_connectivity(true); + opt = get_options("minsize"); if (opt.size()) - filter.set_minsize (opt[0][0]); + filter.set_minsize(opt[0][0]); - Stride::set_from_command_line (filter); + Stride::set_from_command_line(filter); if (largest_only) { filter.datatype() = DataType::UInt8; - auto output_image = Image::create (argument[2], filter); - filter (input_image, output_image); + auto output_image = Image::create(argument[2], filter); + filter(input_image, output_image); } else { filter.datatype() = DataType::UInt32; filter.datatype().set_byte_order_native(); - auto output_image = Image::create (argument[2], filter); - filter (input_image, output_image); + auto output_image = Image::create(argument[2], filter); + filter(input_image, output_image); } return; } if (filter_index == 2) { // Dilate - Filter::Dilate filter (input_image, std::string("applying dilate filter to image ") + Path::basename (argument[0])); - auto opt = get_options ("npass"); + Filter::Dilate filter(input_image, std::string("applying dilate filter to image ") + Path::basename(argument[0])); + auto opt = get_options("npass"); if (opt.size()) - filter.set_npass (int(opt[0][0])); + filter.set_npass(int(opt[0][0])); - Stride::set_from_command_line (filter); + Stride::set_from_command_line(filter); filter.datatype() = DataType::Bit; - auto output_image = Image::create (argument[2], filter); - filter (input_image, output_image); + auto output_image = Image::create(argument[2], filter); + filter(input_image, output_image); return; } if (filter_index == 3) { // Erode - Filter::Erode filter (input_image, std::string("applying erode filter to image ") + Path::basename (argument[0])); - auto opt = get_options ("npass"); + Filter::Erode filter(input_image, std::string("applying erode filter to image ") + Path::basename(argument[0])); + auto opt = get_options("npass"); if (opt.size()) - filter.set_npass (int(opt[0][0])); + filter.set_npass(int(opt[0][0])); - Stride::set_from_command_line (filter); + Stride::set_from_command_line(filter); filter.datatype() = DataType::Bit; - auto output_image = Image::create (argument[2], filter); - filter (input_image, output_image); + auto output_image = Image::create(argument[2], filter); + filter(input_image, output_image); return; } if (filter_index == 4) { // Fill - Filter::Fill filter (input_image, std::string("filling interior of image ") + Path::basename (argument[0])); - auto opt = get_options ("axes"); + Filter::Fill filter(input_image, std::string("filling interior of image ") + Path::basename(argument[0])); + auto opt = get_options("axes"); if (opt.size()) { const vector axes = opt[0][0]; - filter.set_axes (axes); + filter.set_axes(axes); } - opt = get_options ("connectivity"); + opt = get_options("connectivity"); if (opt.size()) - filter.set_26_connectivity (true); - Stride::set_from_command_line (filter); - auto output_image = Image::create (argument[2], filter); - filter (input_image, output_image); + filter.set_26_connectivity(true); + Stride::set_from_command_line(filter); + auto output_image = Image::create(argument[2], filter); + filter(input_image, output_image); return; } if (filter_index == 5) { // Median - Filter::Median filter (input_image, std::string("applying median filter to image ") + Path::basename (argument[0])); - auto opt = get_options ("extent"); + Filter::Median filter(input_image, std::string("applying median filter to image ") + Path::basename(argument[0])); + auto opt = get_options("extent"); if (opt.size()) - filter.set_extent (parse_ints (opt[0][0])); + filter.set_extent(parse_ints(opt[0][0])); - Stride::set_from_command_line (filter); + Stride::set_from_command_line(filter); filter.datatype() = DataType::Bit; - auto output_image = Image::create (argument[2], filter); - filter (input_image, output_image); + auto output_image = Image::create(argument[2], filter); + filter(input_image, output_image); return; } - } diff --git a/cmd/mesh2voxel.cpp b/cmd/mesh2voxel.cpp index 1112f2453b..2c11f0b1d1 100644 --- a/cmd/mesh2voxel.cpp +++ b/cmd/mesh2voxel.cpp @@ -20,48 +20,38 @@ #include "image.h" #include "image_helpers.h" -#include "surface/mesh.h" #include "surface/algo/mesh2image.h" - - - - +#include "surface/mesh.h" using namespace MR; using namespace App; - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Convert a mesh surface to a partial volume estimation image"; REFERENCES - + "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal - "Anatomically-constrained tractography: Improved diffusion MRI streamlines tractography through effective use of anatomical information. " - "NeuroImage, 2012, 62, 1924-1938"; + +"Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal + "Anatomically-constrained tractography: Improved diffusion MRI streamlines tractography through effective use of " + "anatomical information. " + "NeuroImage, 2012, 62, 1924-1938"; ARGUMENTS - + Argument ("source", "the mesh file; note vertices must be defined in realspace coordinates").type_file_in() - + Argument ("template", "the template image").type_image_in() - + Argument ("output", "the output image").type_image_out(); - + +Argument("source", "the mesh file; note vertices must be defined in realspace coordinates").type_file_in() + + Argument("template", "the template image").type_image_in() + + Argument("output", "the output image").type_image_out(); } - - -void run () -{ +void run() { // Read in the mesh data - Surface::Mesh mesh (argument[0]); + Surface::Mesh mesh(argument[0]); // Get the template image - Header template_header = Header::open (argument[1]); - check_3D_nonunity (template_header); + Header template_header = Header::open(argument[1]); + check_3D_nonunity(template_header); // Ensure that a floating-point representation is used for the output image, // as is required for representing partial volumes @@ -69,9 +59,8 @@ void run () template_header.datatype().set_byte_order_native(); // Create the output image - Image output = Image::create (argument[2], template_header); + Image output = Image::create(argument[2], template_header); // Perform the partial volume estimation - Surface::Algo::mesh2image (mesh, output); - + Surface::Algo::mesh2image(mesh, output); } diff --git a/cmd/meshconvert.cpp b/cmd/meshconvert.cpp index 74a7074260..38a09d522d 100644 --- a/cmd/meshconvert.cpp +++ b/cmd/meshconvert.cpp @@ -16,82 +16,81 @@ #include "command.h" #include "header.h" +#include "surface/filter/vertex_transform.h" #include "surface/mesh.h" #include "surface/mesh_multi.h" -#include "surface/filter/vertex_transform.h" - - using namespace MR; using namespace App; using namespace MR::Surface; +const char *transform_choices[] = {"first2real", "real2first", "voxel2real", "real2voxel", "fs2real", nullptr}; - -const char* transform_choices[] = { "first2real", "real2first", "voxel2real", "real2voxel", "fs2real", nullptr }; - - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Convert meshes between different formats, and apply transformations"; ARGUMENTS - + Argument ("input", "the input mesh file").type_file_in() - + Argument ("output", "the output mesh file").type_file_out(); + +Argument("input", "the input mesh file").type_file_in() + Argument("output", "the output mesh file").type_file_out(); OPTIONS - + Option ("binary", "write the output mesh file in binary format (if supported)") - - + Option ("transform", "transform vertices from one coordinate space to another, based on a template image; " - "options are: " + join(transform_choices, ", ")) - + Argument ("mode").type_choice (transform_choices) - + Argument ("image").type_image_in(); + +Option("binary", "write the output mesh file in binary format (if supported)") + + Option("transform", + "transform vertices from one coordinate space to another, based on a template image; " + "options are: " + + join(transform_choices, ", ")) + + Argument("mode").type_choice(transform_choices) + Argument("image").type_image_in(); } - - -void run () -{ +void run() { // Read in the mesh data MeshMulti meshes; try { - MR::Surface::Mesh mesh (argument[0]); - meshes.push_back (mesh); - } catch (Exception& e) { + MR::Surface::Mesh mesh(argument[0]); + meshes.push_back(mesh); + } catch (Exception &e) { try { - meshes.load (argument[0]); + meshes.load(argument[0]); } catch (...) { throw e; } } - auto opt = get_options ("transform"); + auto opt = get_options("transform"); if (opt.size()) { - auto H = Header::open (opt[0][1]); - auto transform = make_unique (H); + auto H = Header::open(opt[0][1]); + auto transform = make_unique(H); switch (int(opt[0][0])) { - case 0: transform->set_first2real(); break; - case 1: transform->set_real2first(); break; - case 2: transform->set_voxel2real(); break; - case 3: transform->set_real2voxel(); break; - case 4: transform->set_fs2real (); break; - default: throw Exception ("Unexpected mode for spatial transformation of vertices"); + case 0: + transform->set_first2real(); + break; + case 1: + transform->set_real2first(); + break; + case 2: + transform->set_voxel2real(); + break; + case 3: + transform->set_real2voxel(); + break; + case 4: + transform->set_fs2real(); + break; + default: + throw Exception("Unexpected mode for spatial transformation of vertices"); } MeshMulti temp; - (*transform) (meshes, temp); - std::swap (meshes, temp); + (*transform)(meshes, temp); + std::swap(meshes, temp); } // Create the output file if (meshes.size() == 1) - meshes[0].save (argument[1], get_options ("binary").size()); + meshes[0].save(argument[1], get_options("binary").size()); else - meshes.save (argument[1]); - + meshes.save(argument[1]); } diff --git a/cmd/meshfilter.cpp b/cmd/meshfilter.cpp index d52955c8bd..763ac06356 100644 --- a/cmd/meshfilter.cpp +++ b/cmd/meshfilter.cpp @@ -18,73 +18,68 @@ #include "progressbar.h" #include "thread_queue.h" -#include "surface/mesh.h" -#include "surface/mesh_multi.h" #include "surface/filter/base.h" #include "surface/filter/smooth.h" - - +#include "surface/mesh.h" +#include "surface/mesh_multi.h" using namespace MR; using namespace App; using namespace MR::Surface; +const char *filters[] = {"smooth", NULL}; -const char* filters[] = { "smooth", NULL }; - +const OptionGroup smooth_option = + OptionGroup("Options for mesh smoothing filter") -const OptionGroup smooth_option = OptionGroup ("Options for mesh smoothing filter") + + Option("smooth_spatial", + "spatial extent of smoothing (default: " + str(Filter::default_smoothing_spatial_factor, 2) + "mm)") + + Argument("value").type_float(0.0) - + Option ("smooth_spatial", "spatial extent of smoothing (default: " + str(Filter::default_smoothing_spatial_factor, 2) + "mm)") - + Argument ("value").type_float (0.0) + + Option("smooth_influence", + "influence factor for smoothing (default: " + str(Filter::default_smoothing_influence_factor, 2) + ")") + + Argument("value").type_float(0.0); - + Option ("smooth_influence", "influence factor for smoothing (default: " + str(Filter::default_smoothing_influence_factor, 2) + ")") - + Argument ("value").type_float (0.0); - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Apply filter operations to meshes"; DESCRIPTION - + "While this command has only one filter operation currently available, it " - "nevertheless presents with a comparable interface to the MRtrix3 commands " - "maskfilter and mrfilter commands."; + +"While this command has only one filter operation currently available, it " + "nevertheless presents with a comparable interface to the MRtrix3 commands " + "maskfilter and mrfilter commands."; EXAMPLES - + Example ("Apply a mesh smoothing filter (currently the only filter available", - "meshfilter input.vtk smooth output.vtk", - "The usage of this command may cause confusion due to the generic interface " - "despite only one filtering operation being currently available. This simple " - "example usage is therefore provided for clarity."); + +Example("Apply a mesh smoothing filter (currently the only filter available", + "meshfilter input.vtk smooth output.vtk", + "The usage of this command may cause confusion due to the generic interface " + "despite only one filtering operation being currently available. This simple " + "example usage is therefore provided for clarity."); ARGUMENTS - + Argument ("input", "the input mesh file").type_file_in() - + Argument ("filter", "the filter to apply." - "Options are: smooth").type_choice (filters) - + Argument ("output", "the output mesh file").type_file_out(); + +Argument("input", "the input mesh file").type_file_in() + + Argument("filter", + "the filter to apply." + "Options are: smooth") + .type_choice(filters) + + Argument("output", "the output mesh file").type_file_out(); OPTIONS - + smooth_option; - + +smooth_option; } - - -void run () -{ +void run() { MeshMulti in; // Read in the mesh data try { - Mesh mesh (argument[0]); - in.push_back (mesh); + Mesh mesh(argument[0]); + in.push_back(mesh); } catch (...) { - in.load (argument[0]); + in.load(argument[0]); } MeshMulti out; @@ -93,21 +88,20 @@ void run () std::unique_ptr filter; int filter_index = argument[1]; if (filter_index == 0) { - const default_type spatial = get_option_value ("smooth_spatial", Filter::default_smoothing_spatial_factor); - const default_type influence = get_option_value ("smooth_influence", Filter::default_smoothing_influence_factor); + const default_type spatial = get_option_value("smooth_spatial", Filter::default_smoothing_spatial_factor); + const default_type influence = get_option_value("smooth_influence", Filter::default_smoothing_influence_factor); const std::string msg = in.size() > 1 ? "Applying smoothing filter to multiple meshes" : ""; - filter.reset (new Filter::Smooth (msg, spatial, influence)); + filter.reset(new Filter::Smooth(msg, spatial, influence)); } else { - assert (0); + assert(0); } - out.assign (in.size(), Mesh()); - (*filter) (in, out); + out.assign(in.size(), Mesh()); + (*filter)(in, out); // Create the output file if (out.size() == 1) - out.front().save (argument[2]); + out.front().save(argument[2]); else - out.save (argument[2]); - + out.save(argument[2]); } diff --git a/cmd/mraverageheader.cpp b/cmd/mraverageheader.cpp index ac6bea6766..af64c4db36 100644 --- a/cmd/mraverageheader.cpp +++ b/cmd/mraverageheader.cpp @@ -14,99 +14,96 @@ * For more details, see http://www.mrtrix.org/. */ +#include "algo/loop.h" #include "command.h" #include "debug.h" #include "image.h" +#include "interp/nearest.h" #include "math/average_space.h" #include "registration/transform/initialiser_helpers.h" -#include "interp/nearest.h" -#include "algo/loop.h" using namespace MR; using namespace App; using namespace Registration; -default_type PADDING_DEFAULT = 0.0; +default_type PADDING_DEFAULT = 0.0; -enum RESOLUTION {MAX, MEAN}; -const char* resolution_choices[] = { "max", "mean", nullptr }; +enum RESOLUTION { MAX, MEAN }; +const char *resolution_choices[] = {"max", "mean", nullptr}; -void usage () -{ +void usage() { AUTHOR = "Maximilian Pietsch (maximilian.pietsch@kcl.ac.uk)"; SYNOPSIS = "Calculate the average (unbiased) coordinate space of all input images"; ARGUMENTS - + Argument ("input", "the input image(s).").type_image_in ().allow_multiple() - + Argument ("output", "the output image").type_image_out (); + +Argument("input", "the input image(s).").type_image_in().allow_multiple() + + Argument("output", "the output image").type_image_out(); OPTIONS - + Option ("padding", " boundary box padding in voxels. Default: " + str(PADDING_DEFAULT)) - + Argument ("value").type_float (0.0, std::numeric_limits::infinity()) - + Option ("resolution", " subsampling of template compared to smallest voxel size in any input image. " - "Valid options are 'mean': unbiased but loss of resolution for individual images possible, " - "and 'max': smallest voxel size of any input image defines the resolution. Default: mean") - + Argument ("type").type_choice (resolution_choices) - + Option ("fill", " set the intensity in the first volume of the average space to 1") - + DataType::options(); + +Option("padding", " boundary box padding in voxels. Default: " + str(PADDING_DEFAULT)) + + Argument("value").type_float(0.0, std::numeric_limits::infinity()) + + Option("resolution", + " subsampling of template compared to smallest voxel size in any input image. " + "Valid options are 'mean': unbiased but loss of resolution for individual images possible, " + "and 'max': smallest voxel size of any input image defines the resolution. Default: mean") + + Argument("type").type_choice(resolution_choices) + + Option("fill", " set the intensity in the first volume of the average space to 1") + DataType::options(); } +void run() { -void run () -{ - - const size_t num_inputs = argument.size()-1; + const size_t num_inputs = argument.size() - 1; - auto opt = get_options ("padding"); - const default_type p = opt.size() ? default_type(opt[0][0]) : PADDING_DEFAULT; - auto padding = Eigen::Matrix(p, p, p, 1.0); - INFO("padding in template voxels: " + str(padding.transpose().head<3>())); + auto opt = get_options("padding"); + const default_type p = opt.size() ? default_type(opt[0][0]) : PADDING_DEFAULT; + auto padding = Eigen::Matrix(p, p, p, 1.0); + INFO("padding in template voxels: " + str(padding.transpose().head<3>())); - opt = get_options ("resolution"); - const int resolution = opt.size() ? int(opt[0][0]) : 1; - INFO("template voxel subsampling: " + str(resolution)); + opt = get_options("resolution"); + const int resolution = opt.size() ? int(opt[0][0]) : 1; + INFO("template voxel subsampling: " + str(resolution)); - bool fill = get_options ("fill").size(); + bool fill = get_options("fill").size(); - vector
headers_in; - size_t dim (Header::open (argument[0]).ndim()); - if (dim < 3 or dim > 4) - throw Exception ("Please provide 3D or 4D images"); - ssize_t volumes (dim == 3 ? 1 : Header::open (argument[0]).size(3)); + vector
headers_in; + size_t dim(Header::open(argument[0]).ndim()); + if (dim < 3 or dim > 4) + throw Exception("Please provide 3D or 4D images"); + ssize_t volumes(dim == 3 ? 1 : Header::open(argument[0]).size(3)); - for (size_t i = 0; i != num_inputs; ++i) { - headers_in.push_back (Header::open (argument[i])); - if (fill) { - if (headers_in.back().ndim() != dim) - throw Exception ("Images do not have the same dimensionality"); - if (dim == 4 and volumes != headers_in.back().size(3)) - throw Exception ("Images do not have the same number of volumes"); - } - } - - vector> transform_header_with; - auto H = compute_minimum_average_header (headers_in, transform_header_with, resolution, padding); - H.datatype() = DataType::Bit; - if (fill) { - H.ndim() = dim; - if (dim == 4) - H.size(3) = headers_in.back().size(3); - } - auto out = Image::create(argument[argument.size()-1], H); + for (size_t i = 0; i != num_inputs; ++i) { + headers_in.push_back(Header::open(argument[i])); if (fill) { - for (auto l = Loop (0,3) (out); l; ++l) - out.value() = 1; - Eigen::Matrix centre, vox; - Registration::Transform::Init::get_geometric_centre (out, centre); - vox = MR::Transform(out).scanner2voxel * centre; - INFO("centre scanner: " + str(centre.transpose())); - for (size_t i = 0; i < 3; ++i) - vox(i) = std::round (vox(i)); - INFO("centre voxel: " + str(vox.transpose())); + if (headers_in.back().ndim() != dim) + throw Exception("Images do not have the same dimensionality"); + if (dim == 4 and volumes != headers_in.back().size(3)) + throw Exception("Images do not have the same number of volumes"); } - INFO("average transformation:"); - INFO(str(out.transform().matrix())); - INFO("average voxel to scanner transformation:"); - INFO(str(MR::Transform(out).voxel2scanner.matrix())); + } + + vector> transform_header_with; + auto H = compute_minimum_average_header(headers_in, transform_header_with, resolution, padding); + H.datatype() = DataType::Bit; + if (fill) { + H.ndim() = dim; + if (dim == 4) + H.size(3) = headers_in.back().size(3); + } + auto out = Image::create(argument[argument.size() - 1], H); + if (fill) { + for (auto l = Loop(0, 3)(out); l; ++l) + out.value() = 1; + Eigen::Matrix centre, vox; + Registration::Transform::Init::get_geometric_centre(out, centre); + vox = MR::Transform(out).scanner2voxel * centre; + INFO("centre scanner: " + str(centre.transpose())); + for (size_t i = 0; i < 3; ++i) + vox(i) = std::round(vox(i)); + INFO("centre voxel: " + str(vox.transpose())); + } + INFO("average transformation:"); + INFO(str(out.transform().matrix())); + INFO("average voxel to scanner transformation:"); + INFO(str(MR::Transform(out).voxel2scanner.matrix())); } diff --git a/cmd/mrcalc.cpp b/cmd/mrcalc.cpp index c512ce1204..5dac20f812 100644 --- a/cmd/mrcalc.cpp +++ b/cmd/mrcalc.cpp @@ -14,9 +14,6 @@ * For more details, see http://www.mrtrix.org/. */ - - - /********************************************************************** CONVENIENCE MACROS: **********************************************************************/ @@ -28,479 +25,610 @@ #undef BINARY_OP #undef TERNARY_OP -# if SECTION == 1 // usage section +#if SECTION == 1 // usage section -#define SECTION_TITLE(TITLE) \ - + OptionGroup (TITLE) +#define SECTION_TITLE(TITLE) +OptionGroup(TITLE) -#define UNARY_OP(OPTION,FEEDBACK,FLAGS,DESCRIPTION,REAL_OPERATION,COMPLEX_OPERATION) \ - + Option (#OPTION, FEEDBACK " : " DESCRIPTION).allow_multiple() +#define UNARY_OP(OPTION, FEEDBACK, FLAGS, DESCRIPTION, REAL_OPERATION, COMPLEX_OPERATION) \ + +Option(#OPTION, FEEDBACK " : " DESCRIPTION).allow_multiple() -#define BINARY_OP(OPTION,FEEDBACK,FLAGS,DESCRIPTION,REAL_OPERATION,COMPLEX_OPERATION) \ - + Option (#OPTION, FEEDBACK " : " DESCRIPTION).allow_multiple() +#define BINARY_OP(OPTION, FEEDBACK, FLAGS, DESCRIPTION, REAL_OPERATION, COMPLEX_OPERATION) \ + +Option(#OPTION, FEEDBACK " : " DESCRIPTION).allow_multiple() -#define TERNARY_OP(OPTION,FEEDBACK,FLAGS,DESCRIPTION,REAL_OPERATION,COMPLEX_OPERATION) \ - + Option (#OPTION, FEEDBACK " : " DESCRIPTION).allow_multiple() +#define TERNARY_OP(OPTION, FEEDBACK, FLAGS, DESCRIPTION, REAL_OPERATION, COMPLEX_OPERATION) \ + +Option(#OPTION, FEEDBACK " : " DESCRIPTION).allow_multiple() -# elif SECTION == 2 // code section +#elif SECTION == 2 // code section #define SECTION_TITLE(TITLE) -#define UNARY_OP(OPTION,FEEDBACK,FLAGS,DESCRIPTION,REAL_OPERATION,COMPLEX_OPERATION) \ - class Op_##OPTION : public OpUnary { \ - public: \ - Op_##OPTION () : OpUnary (FEEDBACK, FLAGS & COMPLEX_MAPS_TO_REAL, FLAGS & REAL_MAPS_TO_COMPLEX) { } \ - complex_type R (real_type v) const REAL_OPERATION \ - complex_type Z (complex_type v) const COMPLEX_OPERATION \ +#define UNARY_OP(OPTION, FEEDBACK, FLAGS, DESCRIPTION, REAL_OPERATION, COMPLEX_OPERATION) \ + class Op_##OPTION : public OpUnary { \ + public: \ + Op_##OPTION() : OpUnary(FEEDBACK, FLAGS & COMPLEX_MAPS_TO_REAL, FLAGS & REAL_MAPS_TO_COMPLEX) {} \ + complex_type R(real_type v) const REAL_OPERATION complex_type Z(complex_type v) const COMPLEX_OPERATION \ }; -#define BINARY_OP(OPTION,FEEDBACK,FLAGS,DESCRIPTION,REAL_OPERATION,COMPLEX_OPERATION) \ - class Op_##OPTION : public OpBinary { \ - public: \ - Op_##OPTION () : OpBinary (FEEDBACK, FLAGS & COMPLEX_MAPS_TO_REAL, FLAGS & REAL_MAPS_TO_COMPLEX) { } \ - complex_type R (real_type a, real_type b) const REAL_OPERATION \ - complex_type Z (complex_type a, complex_type b) const COMPLEX_OPERATION \ +#define BINARY_OP(OPTION, FEEDBACK, FLAGS, DESCRIPTION, REAL_OPERATION, COMPLEX_OPERATION) \ + class Op_##OPTION : public OpBinary { \ + public: \ + Op_##OPTION() : OpBinary(FEEDBACK, FLAGS & COMPLEX_MAPS_TO_REAL, FLAGS & REAL_MAPS_TO_COMPLEX) {} \ + complex_type R(real_type a, real_type b) const REAL_OPERATION complex_type \ + Z(complex_type a, complex_type b) const COMPLEX_OPERATION \ }; -#define TERNARY_OP(OPTION,FEEDBACK,FLAGS,DESCRIPTION,REAL_OPERATION,COMPLEX_OPERATION) \ - class Op_##OPTION : public OpTernary { \ - public: \ - Op_##OPTION () : OpTernary (FEEDBACK, FLAGS & COMPLEX_MAPS_TO_REAL, FLAGS & REAL_MAPS_TO_COMPLEX) { } \ - complex_type R (real_type a, real_type b, real_type c) const REAL_OPERATION \ - complex_type Z (complex_type a, complex_type b, complex_type c) const COMPLEX_OPERATION \ +#define TERNARY_OP(OPTION, FEEDBACK, FLAGS, DESCRIPTION, REAL_OPERATION, COMPLEX_OPERATION) \ + class Op_##OPTION : public OpTernary { \ + public: \ + Op_##OPTION() : OpTernary(FEEDBACK, FLAGS & COMPLEX_MAPS_TO_REAL, FLAGS & REAL_MAPS_TO_COMPLEX) {} \ + complex_type R(real_type a, real_type b, real_type c) const REAL_OPERATION complex_type \ + Z(complex_type a, complex_type b, complex_type c) const COMPLEX_OPERATION \ }; -# elif SECTION == 3 // parsing section +#elif SECTION == 3 // parsing section #define SECTION_TITLE(TITLE) -#define UNARY_OP(OPTION,FEEDBACK,FLAGS,DESCRIPTION,REAL_OPERATION,COMPLEX_OPERATION) \ - else if (opt->is (#OPTION)) unary_operation (opt->id, stack, Op_##OPTION()); +#define UNARY_OP(OPTION, FEEDBACK, FLAGS, DESCRIPTION, REAL_OPERATION, COMPLEX_OPERATION) \ + else if (opt->is(#OPTION)) unary_operation(opt->id, stack, Op_##OPTION()); -#define BINARY_OP(OPTION,FEEDBACK,FLAGS,DESCRIPTION,REAL_OPERATION,COMPLEX_OPERATION) \ - else if (opt->is (#OPTION)) binary_operation (opt->id, stack, Op_##OPTION()); +#define BINARY_OP(OPTION, FEEDBACK, FLAGS, DESCRIPTION, REAL_OPERATION, COMPLEX_OPERATION) \ + else if (opt->is(#OPTION)) binary_operation(opt->id, stack, Op_##OPTION()); -#define TERNARY_OP(OPTION,FEEDBACK,FLAGS,DESCRIPTION,REAL_OPERATION,COMPLEX_OPERATION) \ - else if (opt->is (#OPTION)) ternary_operation (opt->id, stack, Op_##OPTION()); +#define TERNARY_OP(OPTION, FEEDBACK, FLAGS, DESCRIPTION, REAL_OPERATION, COMPLEX_OPERATION) \ + else if (opt->is(#OPTION)) ternary_operation(opt->id, stack, Op_##OPTION()); -# endif +#endif #define NORMAL 0U #define COMPLEX_MAPS_TO_REAL 1U #define REAL_MAPS_TO_COMPLEX 2U -#define NOT_IMPLEMENTED { throw Exception ("operation not supported"); } - - +#define NOT_IMPLEMENTED \ + { throw Exception("operation not supported"); } /********************************************************************** Operations defined below: **********************************************************************/ -SECTION_TITLE ("basic operations") -UNARY_OP (abs, "|%1|", COMPLEX_MAPS_TO_REAL, "return absolute value (magnitude) of real or complex number", { return abs (v); }, { return abs (v); } ) -UNARY_OP (neg, "-%1", NORMAL, "negative value", { return -v; }, { return -v; }) -BINARY_OP (add, "(%1 + %2)", NORMAL, "add values", { return a+b; }, { return a+b; }) -BINARY_OP (subtract, "(%1 - %2)", NORMAL, "subtract nth operand from (n-1)th", { return a-b; }, { return a-b; }) -BINARY_OP (multiply, "(%1 * %2)", NORMAL, "multiply values", { return a*b; }, { return a*b; }) -BINARY_OP (divide, "(%1 / %2)", NORMAL, "divide (n-1)th operand by nth", { return a/b; }, { return a/b; }) -BINARY_OP (modulo, "(%1 \% %2)", NORMAL, "remainder after dividing (n-1)th operand by nth", { return std::fmod(a, b); }, NOT_IMPLEMENTED) -BINARY_OP (min, "min (%1, %2)", NORMAL, "smallest of last two operands", { return std::min (a, b); }, NOT_IMPLEMENTED) -BINARY_OP (max, "max (%1, %2)", NORMAL, "greatest of last two operands", { return std::max (a, b); }, NOT_IMPLEMENTED) - -SECTION_TITLE ("comparison operators") -BINARY_OP (lt, "(%1 < %2)", NORMAL, "less-than operator (true=1, false=0)", { return a < b; }, NOT_IMPLEMENTED) -BINARY_OP (gt, "(%1 > %2)", NORMAL, "greater-than operator (true=1, false=0)", { return a > b; }, NOT_IMPLEMENTED) -BINARY_OP (le, "(%1 <= %2)", NORMAL, "less-than-or-equal-to operator (true=1, false=0)", { return a <= b; }, NOT_IMPLEMENTED) -BINARY_OP (ge, "(%1 >= %2)", NORMAL, "greater-than-or-equal-to operator (true=1, false=0)", { return a >= b; }, NOT_IMPLEMENTED) -BINARY_OP (eq, "(%1 == %2)", COMPLEX_MAPS_TO_REAL, "equal-to operator (true=1, false=0)", { return a == b; }, { return a == b; }) -BINARY_OP (neq, "(%1 != %2)", COMPLEX_MAPS_TO_REAL, "not-equal-to operator (true=1, false=0)", { return a != b; }, { return a != b; }) - -SECTION_TITLE ("conditional operators") -TERNARY_OP (if, "(%1 ? %2 : %3)", NORMAL, "if first operand is true (non-zero), return second operand, otherwise return third operand", { return a ? b : c; }, { return is_true(a) ? b : c; }) -TERNARY_OP (replace, "(%1, %2 -> %3)", NORMAL, "Wherever first operand is equal to the second operand, replace with third operand", { return (a==b) ? c : a; }, { return (a==b) ? c : a; }) - -SECTION_TITLE ("power functions") -UNARY_OP (sqrt, "sqrt (%1)", NORMAL, "square root", { return std::sqrt (v); }, { return std::sqrt (v); }) -BINARY_OP (pow, "%1^%2", NORMAL, "raise (n-1)th operand to nth power", { return std::pow (a, b); }, { return std::pow (a, b); }) - -SECTION_TITLE ("nearest integer operations") -UNARY_OP (round, "round (%1)", NORMAL, "round to nearest integer", { return std::round (v); }, NOT_IMPLEMENTED) -UNARY_OP (ceil, "ceil (%1)", NORMAL, "round up to nearest integer", { return std::ceil (v); }, NOT_IMPLEMENTED) -UNARY_OP (floor, "floor (%1)", NORMAL, "round down to nearest integer", { return std::floor (v); }, NOT_IMPLEMENTED) - -SECTION_TITLE ("logical operators") -UNARY_OP (not, "!%1", NORMAL, "NOT operator: true (1) if operand is false (i.e. zero)", { return !v; }, { return !is_true (v); }) -BINARY_OP (and, "(%1 && %2)", NORMAL, "AND operator: true (1) if both operands are true (i.e. non-zero)", { return a && b; }, { return is_true(a) && is_true(b); }) -BINARY_OP (or, "(%1 || %2)", NORMAL, "OR operator: true (1) if either operand is true (i.e. non-zero)", { return a || b; }, { return is_true(a) || is_true(b); }) -BINARY_OP (xor, "(%1 ^^ %2)", NORMAL, "XOR operator: true (1) if only one of the operands is true (i.e. non-zero)", { return (!a) != (!b); }, { return is_true(a) != is_true(b); }) - -SECTION_TITLE ("classification functions") -UNARY_OP (isnan, "isnan (%1)", COMPLEX_MAPS_TO_REAL, "true (1) if operand is not-a-number (NaN)", { return std::isnan (v); }, { return std::isnan (v.real()) || std::isnan (v.imag()); }) -UNARY_OP (isinf, "isinf (%1)", COMPLEX_MAPS_TO_REAL, "true (1) if operand is infinite (Inf)", { return std::isinf (v); }, { return std::isinf (v.real()) || std::isinf (v.imag()); }) -UNARY_OP (finite, "finite (%1)", COMPLEX_MAPS_TO_REAL, "true (1) if operand is finite (i.e. not NaN or Inf)", { return std::isfinite (v); }, { return std::isfinite (v.real()) && std::isfinite (v.imag()); }) - -SECTION_TITLE ("complex numbers") -BINARY_OP (complex, "(%1 + %2 i)", REAL_MAPS_TO_COMPLEX, "create complex number using the last two operands as real,imaginary components", { return complex_type (a, b); }, NOT_IMPLEMENTED) -BINARY_OP (polar, "(%1 /_ %2)", REAL_MAPS_TO_COMPLEX, "create complex number using the last two operands as magnitude,phase components (phase in radians)", { return std::polar (a, b); }, NOT_IMPLEMENTED) -UNARY_OP (real, "real (%1)", COMPLEX_MAPS_TO_REAL, "real part of complex number", { return v; }, { return v.real(); }) -UNARY_OP (imag, "imag (%1)", COMPLEX_MAPS_TO_REAL, "imaginary part of complex number", { return 0.0; }, { return v.imag(); }) -UNARY_OP (phase, "phase (%1)", COMPLEX_MAPS_TO_REAL, "phase of complex number (use -abs for magnitude)", { return v < 0.0 ? Math::pi : 0.0; }, { return std::arg (v); }) -UNARY_OP (conj, "conj (%1)", NORMAL, "complex conjugate", { return v; }, { return std::conj (v); }) -UNARY_OP (proj, "proj (%1)", REAL_MAPS_TO_COMPLEX, "projection onto the Riemann sphere", { return std::proj (v); }, { return std::proj (v); }) - -SECTION_TITLE ("exponential functions") -UNARY_OP (exp, "exp (%1)", NORMAL, "exponential function", { return std::exp (v); }, { return std::exp (v); }) -UNARY_OP (log, "log (%1)", NORMAL, "natural logarithm", { return std::log (v); }, { return std::log (v); }) -UNARY_OP (log10, "log10 (%1)", NORMAL, "common logarithm", { return std::log10 (v); }, { return std::log10 (v); }) - -SECTION_TITLE ("trigonometric functions") -UNARY_OP (cos, "cos (%1)", NORMAL, "cosine", { return std::cos (v); }, { return std::cos (v); }) -UNARY_OP (sin, "sin (%1)", NORMAL, "sine", { return std::sin (v); }, { return std::sin (v); }) -UNARY_OP (tan, "tan (%1)", NORMAL, "tangent", { return std::tan (v); }, { return std::tan (v); }) -UNARY_OP (acos, "acos (%1)", NORMAL, "inverse cosine", { return std::acos (v); }, { return std::acos (v); }) -UNARY_OP (asin, "asin (%1)", NORMAL, "inverse sine", { return std::asin (v); }, { return std::asin (v); }) -UNARY_OP (atan, "atan (%1)", NORMAL, "inverse tangent", { return std::atan (v); }, { return std::atan (v); }) - -SECTION_TITLE ("hyperbolic functions") -UNARY_OP (cosh, "cosh (%1)", NORMAL, "hyperbolic cosine", { return std::cosh (v); }, { return std::cosh (v); }) -UNARY_OP (sinh, "sinh (%1)", NORMAL, "hyperbolic sine", { return std::sinh (v); }, { return std::sinh (v); }) -UNARY_OP (tanh, "tanh (%1)", NORMAL, "hyperbolic tangent", { return std::tanh (v); }, { return std::tanh (v); }) -UNARY_OP (acosh, "acosh (%1)", NORMAL, "inverse hyperbolic cosine", { return std::acosh (v); }, { return std::acosh (v); }) -UNARY_OP (asinh, "asinh (%1)", NORMAL, "inverse hyperbolic sine", { return std::asinh (v); }, { return std::asinh (v); }) -UNARY_OP (atanh, "atanh (%1)", NORMAL, "inverse hyperbolic tangent", { return std::atanh (v); }, { return std::atanh (v); }) - - +SECTION_TITLE("basic operations") +UNARY_OP( + abs, + "|%1|", + COMPLEX_MAPS_TO_REAL, + "return absolute value (magnitude) of real or complex number", + { return abs(v); }, + { return abs(v); }) +UNARY_OP( + neg, "-%1", NORMAL, "negative value", { return -v; }, { return -v; }) +BINARY_OP( + add, "(%1 + %2)", NORMAL, "add values", { return a + b; }, { return a + b; }) +BINARY_OP( + subtract, "(%1 - %2)", NORMAL, "subtract nth operand from (n-1)th", { return a - b; }, { return a - b; }) +BINARY_OP( + multiply, "(%1 * %2)", NORMAL, "multiply values", { return a * b; }, { return a * b; }) +BINARY_OP( + divide, "(%1 / %2)", NORMAL, "divide (n-1)th operand by nth", { return a / b; }, { return a / b; }) +BINARY_OP( + modulo, + "(%1 \% %2)", + NORMAL, + "remainder after dividing (n-1)th operand by nth", + { return std::fmod(a, b); }, + NOT_IMPLEMENTED) +BINARY_OP( + min, "min (%1, %2)", NORMAL, "smallest of last two operands", { return std::min(a, b); }, NOT_IMPLEMENTED) +BINARY_OP( + max, "max (%1, %2)", NORMAL, "greatest of last two operands", { return std::max(a, b); }, NOT_IMPLEMENTED) + +SECTION_TITLE("comparison operators") +BINARY_OP( + lt, "(%1 < %2)", NORMAL, "less-than operator (true=1, false=0)", { return a < b; }, NOT_IMPLEMENTED) +BINARY_OP( + gt, "(%1 > %2)", NORMAL, "greater-than operator (true=1, false=0)", { return a > b; }, NOT_IMPLEMENTED) +BINARY_OP( + le, "(%1 <= %2)", NORMAL, "less-than-or-equal-to operator (true=1, false=0)", { return a <= b; }, NOT_IMPLEMENTED) +BINARY_OP( + ge, + "(%1 >= %2)", + NORMAL, + "greater-than-or-equal-to operator (true=1, false=0)", + { return a >= b; }, + NOT_IMPLEMENTED) +BINARY_OP( + eq, + "(%1 == %2)", + COMPLEX_MAPS_TO_REAL, + "equal-to operator (true=1, false=0)", + { return a == b; }, + { return a == b; }) +BINARY_OP( + neq, + "(%1 != %2)", + COMPLEX_MAPS_TO_REAL, + "not-equal-to operator (true=1, false=0)", + { return a != b; }, + { return a != b; }) + +SECTION_TITLE("conditional operators") +TERNARY_OP( + if, + "(%1 ? %2 : %3)", + NORMAL, + "if first operand is true (non-zero), return second operand, otherwise return third operand", + { return a ? b : c; }, + { return is_true(a) ? b : c; }) +TERNARY_OP( + replace, + "(%1, %2 -> %3)", + NORMAL, + "Wherever first operand is equal to the second operand, replace with third operand", + { return (a == b) ? c : a; }, + { return (a == b) ? c : a; }) + +SECTION_TITLE("power functions") +UNARY_OP( + sqrt, "sqrt (%1)", NORMAL, "square root", { return std::sqrt(v); }, { return std::sqrt(v); }) +BINARY_OP( + pow, "%1^%2", NORMAL, "raise (n-1)th operand to nth power", { return std::pow(a, b); }, { return std::pow(a, b); }) + +SECTION_TITLE("nearest integer operations") +UNARY_OP( + round, "round (%1)", NORMAL, "round to nearest integer", { return std::round(v); }, NOT_IMPLEMENTED) +UNARY_OP( + ceil, "ceil (%1)", NORMAL, "round up to nearest integer", { return std::ceil(v); }, NOT_IMPLEMENTED) +UNARY_OP( + floor, "floor (%1)", NORMAL, "round down to nearest integer", { return std::floor(v); }, NOT_IMPLEMENTED) + +SECTION_TITLE("logical operators") +UNARY_OP( + not, + "!%1", + NORMAL, + "NOT operator: true (1) if operand is false (i.e. zero)", + { return !v; }, + { return !is_true(v); }) +BINARY_OP( + and, + "(%1 && %2)", + NORMAL, + "AND operator: true (1) if both operands are true (i.e. non-zero)", + { return a && b; }, + { return is_true(a) && is_true(b); }) +BINARY_OP( + or, + "(%1 || %2)", + NORMAL, + "OR operator: true (1) if either operand is true (i.e. non-zero)", + { return a || b; }, + { return is_true(a) || is_true(b); }) +BINARY_OP( + xor, + "(%1 ^^ %2)", + NORMAL, + "XOR operator: true (1) if only one of the operands is true (i.e. non-zero)", + { + return (!a) != (!b); + }, + { return is_true(a) != is_true(b); }) + +SECTION_TITLE("classification functions") +UNARY_OP( + isnan, + "isnan (%1)", + COMPLEX_MAPS_TO_REAL, + "true (1) if operand is not-a-number (NaN)", + { return std::isnan(v); }, + { return std::isnan(v.real()) || std::isnan(v.imag()); }) +UNARY_OP( + isinf, + "isinf (%1)", + COMPLEX_MAPS_TO_REAL, + "true (1) if operand is infinite (Inf)", + { return std::isinf(v); }, + { return std::isinf(v.real()) || std::isinf(v.imag()); }) +UNARY_OP( + finite, + "finite (%1)", + COMPLEX_MAPS_TO_REAL, + "true (1) if operand is finite (i.e. not NaN or Inf)", + { return std::isfinite(v); }, + { return std::isfinite(v.real()) && std::isfinite(v.imag()); }) + +SECTION_TITLE("complex numbers") +BINARY_OP( + complex, + "(%1 + %2 i)", + REAL_MAPS_TO_COMPLEX, + "create complex number using the last two operands as real,imaginary components", + { return complex_type(a, b); }, + NOT_IMPLEMENTED) +BINARY_OP( + polar, + "(%1 /_ %2)", + REAL_MAPS_TO_COMPLEX, + "create complex number using the last two operands as magnitude,phase components (phase in radians)", + { return std::polar(a, b); }, + NOT_IMPLEMENTED) +UNARY_OP( + real, "real (%1)", COMPLEX_MAPS_TO_REAL, "real part of complex number", { return v; }, { return v.real(); }) +UNARY_OP( + imag, "imag (%1)", COMPLEX_MAPS_TO_REAL, "imaginary part of complex number", { return 0.0; }, { return v.imag(); }) +UNARY_OP( + phase, + "phase (%1)", + COMPLEX_MAPS_TO_REAL, + "phase of complex number (use -abs for magnitude)", + { return v < 0.0 ? Math::pi : 0.0; }, + { return std::arg(v); }) +UNARY_OP( + conj, "conj (%1)", NORMAL, "complex conjugate", { return v; }, { return std::conj(v); }) +UNARY_OP( + proj, + "proj (%1)", + REAL_MAPS_TO_COMPLEX, + "projection onto the Riemann sphere", + { return std::proj(v); }, + { return std::proj(v); }) + +SECTION_TITLE("exponential functions") +UNARY_OP( + exp, "exp (%1)", NORMAL, "exponential function", { return std::exp(v); }, { return std::exp(v); }) +UNARY_OP( + log, "log (%1)", NORMAL, "natural logarithm", { return std::log(v); }, { return std::log(v); }) +UNARY_OP( + log10, "log10 (%1)", NORMAL, "common logarithm", { return std::log10(v); }, { return std::log10(v); }) + +SECTION_TITLE("trigonometric functions") +UNARY_OP( + cos, "cos (%1)", NORMAL, "cosine", { return std::cos(v); }, { return std::cos(v); }) +UNARY_OP( + sin, "sin (%1)", NORMAL, "sine", { return std::sin(v); }, { return std::sin(v); }) +UNARY_OP( + tan, "tan (%1)", NORMAL, "tangent", { return std::tan(v); }, { return std::tan(v); }) +UNARY_OP( + acos, "acos (%1)", NORMAL, "inverse cosine", { return std::acos(v); }, { return std::acos(v); }) +UNARY_OP( + asin, "asin (%1)", NORMAL, "inverse sine", { return std::asin(v); }, { return std::asin(v); }) +UNARY_OP( + atan, "atan (%1)", NORMAL, "inverse tangent", { return std::atan(v); }, { return std::atan(v); }) + +SECTION_TITLE("hyperbolic functions") +UNARY_OP( + cosh, "cosh (%1)", NORMAL, "hyperbolic cosine", { return std::cosh(v); }, { return std::cosh(v); }) +UNARY_OP( + sinh, "sinh (%1)", NORMAL, "hyperbolic sine", { return std::sinh(v); }, { return std::sinh(v); }) +UNARY_OP( + tanh, "tanh (%1)", NORMAL, "hyperbolic tangent", { return std::tanh(v); }, { return std::tanh(v); }) +UNARY_OP( + acosh, "acosh (%1)", NORMAL, "inverse hyperbolic cosine", { return std::acosh(v); }, { return std::acosh(v); }) +UNARY_OP( + asinh, "asinh (%1)", NORMAL, "inverse hyperbolic sine", { return std::asinh(v); }, { return std::asinh(v); }) +UNARY_OP( + atanh, "atanh (%1)", NORMAL, "inverse hyperbolic tangent", { return std::atanh(v); }, { return std::atanh(v); }) #undef SECTION #else - /********************************************************************** Main program **********************************************************************/ +#include "algo/threaded_copy.h" #include "command.h" +#include "dwi/gradient.h" #include "image.h" +#include "math/rng.h" #include "memory.h" #include "phase_encoding.h" -#include "math/rng.h" -#include "algo/threaded_copy.h" -#include "dwi/gradient.h" using namespace MR; using namespace App; - using real_type = float; using complex_type = cfloat; -static bool transform_mis_match_reported (false); - -inline bool is_true (const complex_type& z) { return z.real() || z.imag(); } - - -void usage () { - -AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; - -SYNOPSIS = "Apply generic voxel-wise mathematical operations to images"; - -DESCRIPTION - + "This command will only compute per-voxel operations. " - "Use 'mrmath' to compute summary statistics across images or " - "along image axes." - - + "This command uses a stack-based syntax, with operators " - "(specified using options) operating on the top-most entries " - "(i.e. images or values) in the stack. Operands (values or " - "images) are pushed onto the stack in the order they appear " - "(as arguments) on the command-line, and operators (specified " - "as options) operate on and consume the top-most entries in " - "the stack, and push their output as a new entry on the stack." - - + "As an additional feature, this command will allow images with different " - "dimensions to be processed, provided they satisfy the following " - "conditions: for each axis, the dimensions match if they are the same size, " - "or one of them has size one. In the latter case, the entire image will be " - "replicated along that axis. This allows for example a 4D image of " - "size [ X Y Z N ] to be added to a 3D image of size [ X Y Z ], as if it " - "consisted of N copies of the 3D image along the 4th axis (the missing " - "dimension is assumed to have size 1). Another example would a " - "single-voxel 4D image of size [ 1 1 1 N ], multiplied by a 3D image of " - "size [ X Y Z ], which would allow the creation of a 4D image where each " - "volume consists of the 3D image scaled by the corresponding value for " - "that volume in the single-voxel image."; - -EXAMPLES - + Example ("Double the value stored in every voxel", - "mrcalc a.mif 2 -mult r.mif", - "This performs the operation: r = 2*a for every voxel a,r in " - "images a.mif and r.mif respectively.") - - + Example ("A more complex example", - "mrcalc a.mif -neg b.mif -div -exp 9.3 -mult r.mif", - "This performs the operation: r = 9.3*exp(-a/b)") - - + Example ("Another complex example", - "mrcalc a.mif b.mif -add c.mif d.mif -mult 4.2 -add -div r.mif", - "This performs: r = (a+b)/(c*d+4.2).") - - + Example ("Rescale the densities in a SH l=0 image", - "mrcalc ODF_CSF.mif 4 pi -mult -sqrt -div ODF_CSF_scaled.mif", - "This applies the spherical harmonic basis scaling factor: " - "1.0/sqrt(4*pi), such that a single-tissue voxel containing the " - "same intensities as the response function of that tissue " - "should contain the value 1.0."); - -ARGUMENTS - + Argument ("operand", "an input image, intensity value, or the special keywords " - "'rand' (random number between 0 and 1) or 'randn' (random number from unit " - "std.dev. normal distribution) or the mathematical constants 'e' and 'pi'.").type_various().allow_multiple(); - -OPTIONS +static bool transform_mis_match_reported(false); + +inline bool is_true(const complex_type &z) { return z.real() || z.imag(); } + +void usage() { + + AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; + + SYNOPSIS = "Apply generic voxel-wise mathematical operations to images"; + + DESCRIPTION + +"This command will only compute per-voxel operations. " + "Use 'mrmath' to compute summary statistics across images or " + "along image axes." + + + "This command uses a stack-based syntax, with operators " + "(specified using options) operating on the top-most entries " + "(i.e. images or values) in the stack. Operands (values or " + "images) are pushed onto the stack in the order they appear " + "(as arguments) on the command-line, and operators (specified " + "as options) operate on and consume the top-most entries in " + "the stack, and push their output as a new entry on the stack." + + + "As an additional feature, this command will allow images with different " + "dimensions to be processed, provided they satisfy the following " + "conditions: for each axis, the dimensions match if they are the same size, " + "or one of them has size one. In the latter case, the entire image will be " + "replicated along that axis. This allows for example a 4D image of " + "size [ X Y Z N ] to be added to a 3D image of size [ X Y Z ], as if it " + "consisted of N copies of the 3D image along the 4th axis (the missing " + "dimension is assumed to have size 1). Another example would a " + "single-voxel 4D image of size [ 1 1 1 N ], multiplied by a 3D image of " + "size [ X Y Z ], which would allow the creation of a 4D image where each " + "volume consists of the 3D image scaled by the corresponding value for " + "that volume in the single-voxel image."; + + EXAMPLES + +Example("Double the value stored in every voxel", + "mrcalc a.mif 2 -mult r.mif", + "This performs the operation: r = 2*a for every voxel a,r in " + "images a.mif and r.mif respectively.") + + + Example("A more complex example", + "mrcalc a.mif -neg b.mif -div -exp 9.3 -mult r.mif", + "This performs the operation: r = 9.3*exp(-a/b)") + + + Example("Another complex example", + "mrcalc a.mif b.mif -add c.mif d.mif -mult 4.2 -add -div r.mif", + "This performs: r = (a+b)/(c*d+4.2).") + + + Example("Rescale the densities in a SH l=0 image", + "mrcalc ODF_CSF.mif 4 pi -mult -sqrt -div ODF_CSF_scaled.mif", + "This applies the spherical harmonic basis scaling factor: " + "1.0/sqrt(4*pi), such that a single-tissue voxel containing the " + "same intensities as the response function of that tissue " + "should contain the value 1.0."); + + ARGUMENTS + +Argument("operand", + "an input image, intensity value, or the special keywords " + "'rand' (random number between 0 and 1) or 'randn' (random number from unit " + "std.dev. normal distribution) or the mathematical constants 'e' and 'pi'.") + .type_various() + .allow_multiple(); + + OPTIONS #define SECTION 1 #include "mrcalc.cpp" - + DataType::options(); + +DataType::options(); } - - - /********************************************************************** STACK FRAMEWORK: **********************************************************************/ - class Evaluator; - -class Chunk : public vector { - public: - complex_type value; +class Chunk : public vector { +public: + complex_type value; }; - -class ThreadLocalStorageItem { - public: - Chunk chunk; - copy_ptr> image; +class ThreadLocalStorageItem { +public: + Chunk chunk; + copy_ptr> image; }; -class ThreadLocalStorage : public vector { - public: - - void load (Chunk& chunk, Image& image) { - for (size_t n = 0; n < image.ndim(); ++n) - if (image.size(n) > 1) - image.index(n) = iter->index(n); - - size_t n = 0; - for (size_t y = 0; y < size[1]; ++y) { - if (axes[1] < image.ndim()) if (image.size (axes[1]) > 1) image.index(axes[1]) = y; - for (size_t x = 0; x < size[0]; ++x) { - if (axes[0] < image.ndim()) if (image.size (axes[0]) > 1) image.index(axes[0]) = x; - chunk[n++] = image.value(); - } - } +class ThreadLocalStorage : public vector { +public: + void load(Chunk &chunk, Image &image) { + for (size_t n = 0; n < image.ndim(); ++n) + if (image.size(n) > 1) + image.index(n) = iter->index(n); + + size_t n = 0; + for (size_t y = 0; y < size[1]; ++y) { + if (axes[1] < image.ndim()) + if (image.size(axes[1]) > 1) + image.index(axes[1]) = y; + for (size_t x = 0; x < size[0]; ++x) { + if (axes[0] < image.ndim()) + if (image.size(axes[0]) > 1) + image.index(axes[0]) = x; + chunk[n++] = image.value(); } - - Chunk& next () { - ThreadLocalStorageItem& item ((*this)[current++]); - if (item.image) load (item.chunk, *item.image); - return item.chunk; } + } - void reset (const Iterator& current_position) { current = 0; iter = ¤t_position; } - - const Iterator* iter; - vector axes, size; - - private: - size_t current; -}; - + Chunk &next() { + ThreadLocalStorageItem &item((*this)[current++]); + if (item.image) + load(item.chunk, *item.image); + return item.chunk; + } + void reset(const Iterator ¤t_position) { + current = 0; + iter = ¤t_position; + } + const Iterator *iter; + vector axes, size; -class LoadedImage { - public: - LoadedImage (std::shared_ptr>& i, const bool c) : - image (i), - image_is_complex (c) { } - std::shared_ptr> image; - bool image_is_complex; +private: + size_t current; }; +class LoadedImage { +public: + LoadedImage(std::shared_ptr> &i, const bool c) : image(i), image_is_complex(c) {} + std::shared_ptr> image; + bool image_is_complex; +}; +class StackEntry { +public: + StackEntry(const char *entry) : arg(entry), rng_gaussian(false), image_is_complex(false) {} + StackEntry(Evaluator *evaluator_p) + : arg(nullptr), evaluator(evaluator_p), rng_gaussian(false), image_is_complex(false) {} -class StackEntry { - public: - - StackEntry (const char* entry) : - arg (entry), - rng_gaussian (false), - image_is_complex (false) { } - - StackEntry (Evaluator* evaluator_p) : - arg (nullptr), - evaluator (evaluator_p), - rng_gaussian (false), - image_is_complex (false) { } - - void load () { - if (!arg) - return; - auto search = image_list.find (arg); - if (search != image_list.end()) { - DEBUG (std::string ("image \"") + arg + "\" already loaded - re-using exising image"); - image = search->second.image; - image_is_complex = search->second.image_is_complex; - } - else { + void load() { + if (!arg) + return; + auto search = image_list.find(arg); + if (search != image_list.end()) { + DEBUG(std::string("image \"") + arg + "\" already loaded - re-using exising image"); + image = search->second.image; + image_is_complex = search->second.image_is_complex; + } else { + try { + auto header = Header::open(arg); + image_is_complex = header.datatype().is_complex(); + image.reset(new Image(header.get_image())); + image_list.insert(std::make_pair(arg, LoadedImage(image, image_is_complex))); + } catch (Exception &e_image) { try { - auto header = Header::open (arg); - image_is_complex = header.datatype().is_complex(); - image.reset (new Image (header.get_image())); - image_list.insert (std::make_pair (arg, LoadedImage (image, image_is_complex))); - } - catch (Exception& e_image) { - try { - std::string a = lowercase (arg); - if (a == "pi") { value = Math::pi; } - else if (a == "e") { value = Math::e; } - else if (a == "rand") { value = 0.0; rng.reset (new Math::RNG()); rng_gaussian = false; } - else if (a == "randn") { value = 0.0; rng.reset (new Math::RNG()); rng_gaussian = true; } - else { value = to (arg); } - } catch (Exception& e_number) { - Exception e (std::string ("Could not interpret string \"") + arg + "\" as either an image path or a numerical value"); - e.push_back ("As image: "); - for (size_t i = 0; i != e_image.num(); ++i) - e.push_back (e_image[i]); - e.push_back ("As numerical value: "); - for (size_t i = 0; i != e_number.num(); ++i) - e.push_back (e_number[i]); - throw e; + std::string a = lowercase(arg); + if (a == "pi") { + value = Math::pi; + } else if (a == "e") { + value = Math::e; + } else if (a == "rand") { + value = 0.0; + rng.reset(new Math::RNG()); + rng_gaussian = false; + } else if (a == "randn") { + value = 0.0; + rng.reset(new Math::RNG()); + rng_gaussian = true; + } else { + value = to(arg); } + } catch (Exception &e_number) { + Exception e(std::string("Could not interpret string \"") + arg + + "\" as either an image path or a numerical value"); + e.push_back("As image: "); + for (size_t i = 0; i != e_image.num(); ++i) + e.push_back(e_image[i]); + e.push_back("As numerical value: "); + for (size_t i = 0; i != e_number.num(); ++i) + e.push_back(e_number[i]); + throw e; } } - arg = nullptr; } + arg = nullptr; + } - const char* arg; - std::shared_ptr evaluator; - std::shared_ptr> image; - copy_ptr rng; - complex_type value; - bool rng_gaussian; - bool image_is_complex; + const char *arg; + std::shared_ptr evaluator; + std::shared_ptr> image; + copy_ptr rng; + complex_type value; + bool rng_gaussian; + bool image_is_complex; - bool is_complex () const; + bool is_complex() const; - static std::map image_list; + static std::map image_list; - Chunk& evaluate (ThreadLocalStorage& storage) const; + Chunk &evaluate(ThreadLocalStorage &storage) const; }; std::map StackEntry::image_list; +class Evaluator { +public: + Evaluator(const std::string &name, + const char *format_string, + bool complex_maps_to_real = false, + bool real_maps_to_complex = false) + : id(name), format(format_string), ZtoR(complex_maps_to_real), RtoZ(real_maps_to_complex) {} + virtual ~Evaluator() {} + const std::string id; + const char *format; + bool ZtoR, RtoZ; + vector operands; + + Chunk &evaluate(ThreadLocalStorage &storage) const { + Chunk &in1(operands[0].evaluate(storage)); + if (num_args() == 1) + return evaluate(in1); + Chunk &in2(operands[1].evaluate(storage)); + if (num_args() == 2) + return evaluate(in1, in2); + Chunk &in3(operands[2].evaluate(storage)); + return evaluate(in1, in2, in3); + } + virtual Chunk &evaluate(Chunk &in) const { + throw Exception("operation \"" + id + "\" not supported!"); + return in; + } + virtual Chunk &evaluate(Chunk &a, Chunk &b) const { + throw Exception("operation \"" + id + "\" not supported!"); + return a; + } + virtual Chunk &evaluate(Chunk &a, Chunk &b, Chunk &c) const { + throw Exception("operation \"" + id + "\" not supported!"); + return a; + } -class Evaluator { - public: - Evaluator (const std::string& name, const char* format_string, bool complex_maps_to_real = false, bool real_maps_to_complex = false) : - id (name), - format (format_string), - ZtoR (complex_maps_to_real), - RtoZ (real_maps_to_complex) { } - virtual ~Evaluator() { } - const std::string id; - const char* format; - bool ZtoR, RtoZ; - vector operands; - - Chunk& evaluate (ThreadLocalStorage& storage) const { - Chunk& in1 (operands[0].evaluate (storage)); - if (num_args() == 1) return evaluate (in1); - Chunk& in2 (operands[1].evaluate (storage)); - if (num_args() == 2) return evaluate (in1, in2); - Chunk& in3 (operands[2].evaluate (storage)); - return evaluate (in1, in2, in3); - } - virtual Chunk& evaluate (Chunk& in) const { throw Exception ("operation \"" + id + "\" not supported!"); return in; } - virtual Chunk& evaluate (Chunk& a, Chunk& b) const { throw Exception ("operation \"" + id + "\" not supported!"); return a; } - virtual Chunk& evaluate (Chunk& a, Chunk& b, Chunk& c) const { throw Exception ("operation \"" + id + "\" not supported!"); return a; } - - virtual bool is_complex () const { - for (size_t n = 0; n < operands.size(); ++n) - if (operands[n].is_complex()) - return !ZtoR; - return RtoZ; - } - size_t num_args () const { return operands.size(); } - + virtual bool is_complex() const { + for (size_t n = 0; n < operands.size(); ++n) + if (operands[n].is_complex()) + return !ZtoR; + return RtoZ; + } + size_t num_args() const { return operands.size(); } }; - - -inline bool StackEntry::is_complex () const { - if (image) return image_is_complex; - if (evaluator) return evaluator->is_complex(); - if (rng) return false; +inline bool StackEntry::is_complex() const { + if (image) + return image_is_complex; + if (evaluator) + return evaluator->is_complex(); + if (rng) + return false; return value.imag() != 0.0; } - - -inline Chunk& StackEntry::evaluate (ThreadLocalStorage& storage) const -{ - if (evaluator) return evaluator->evaluate (storage); +inline Chunk &StackEntry::evaluate(ThreadLocalStorage &storage) const { + if (evaluator) + return evaluator->evaluate(storage); if (rng) { - Chunk& chunk = storage.next(); + Chunk &chunk = storage.next(); if (rng_gaussian) { - std::normal_distribution dis (0.0, 1.0); + std::normal_distribution dis(0.0, 1.0); for (size_t n = 0; n < chunk.size(); ++n) - chunk[n] = dis (*rng); - } - else { - std::uniform_real_distribution dis (0.0, 1.0); + chunk[n] = dis(*rng); + } else { + std::uniform_real_distribution dis(0.0, 1.0); for (size_t n = 0; n < chunk.size(); ++n) - chunk[n] = dis (*rng); + chunk[n] = dis(*rng); } return chunk; } return storage.next(); } - - - - - - -inline void replace (std::string& orig, size_t n, const std::string& value) -{ - if (orig[0] == '(' && orig[orig.size()-1] == ')') { - size_t pos = orig.find ("(%"+str(n+1)+")"); +inline void replace(std::string &orig, size_t n, const std::string &value) { + if (orig[0] == '(' && orig[orig.size() - 1] == ')') { + size_t pos = orig.find("(%" + str(n + 1) + ")"); if (pos != orig.npos) { - orig.replace (pos, 4, value); + orig.replace(pos, 4, value); return; } } - size_t pos = orig.find ("%"+str(n+1)); + size_t pos = orig.find("%" + str(n + 1)); if (pos != orig.npos) - orig.replace (pos, 2, value); + orig.replace(pos, 2, value); } - // TODO: move this into StackEntry class and compute string at construction // to make sure full operation is recorded, even for scalar operations that // get evaluated there and then and so get left out if the string is created // later: -std::string operation_string (const StackEntry& entry) -{ +std::string operation_string(const StackEntry &entry) { if (entry.image) return entry.image->name(); else if (entry.rng) @@ -508,205 +636,155 @@ std::string operation_string (const StackEntry& entry) else if (entry.evaluator) { std::string s = entry.evaluator->format; for (size_t n = 0; n < entry.evaluator->operands.size(); ++n) - replace (s, n, operation_string (entry.evaluator->operands[n])); + replace(s, n, operation_string(entry.evaluator->operands[n])); return s; - } - else return str(entry.value); + } else + return str(entry.value); } +template class UnaryEvaluator : public Evaluator { +public: + UnaryEvaluator(const std::string &name, Operation operation, const StackEntry &operand) + : Evaluator(name, operation.format, operation.ZtoR, operation.RtoZ), op(operation) { + operands.push_back(operand); + } + Operation op; + virtual Chunk &evaluate(Chunk &in) const { + if (operands[0].is_complex()) + for (size_t n = 0; n < in.size(); ++n) + in[n] = op.Z(in[n]); + else + for (size_t n = 0; n < in.size(); ++n) + in[n] = op.R(in[n].real()); - -template -class UnaryEvaluator : public Evaluator { - public: - UnaryEvaluator (const std::string& name, Operation operation, const StackEntry& operand) : - Evaluator (name, operation.format, operation.ZtoR, operation.RtoZ), - op (operation) { - operands.push_back (operand); - } - - Operation op; - - virtual Chunk& evaluate (Chunk& in) const { - if (operands[0].is_complex()) - for (size_t n = 0; n < in.size(); ++n) - in[n] = op.Z (in[n]); - else - for (size_t n = 0; n < in.size(); ++n) - in[n] = op.R (in[n].real()); - - return in; - } + return in; + } }; +template class BinaryEvaluator : public Evaluator { +public: + BinaryEvaluator(const std::string &name, Operation operation, const StackEntry &operand1, const StackEntry &operand2) + : Evaluator(name, operation.format, operation.ZtoR, operation.RtoZ), op(operation) { + operands.push_back(operand1); + operands.push_back(operand2); + } + Operation op; - - - -template -class BinaryEvaluator : public Evaluator { - public: - BinaryEvaluator (const std::string& name, Operation operation, const StackEntry& operand1, const StackEntry& operand2) : - Evaluator (name, operation.format, operation.ZtoR, operation.RtoZ), - op (operation) { - operands.push_back (operand1); - operands.push_back (operand2); - } - - Operation op; - - virtual Chunk& evaluate (Chunk& a, Chunk& b) const { - Chunk& out (a.size() ? a : b); - if (operands[0].is_complex() || operands[1].is_complex()) { - for (size_t n = 0; n < out.size(); ++n) - out[n] = op.Z ( - a.size() ? a[n] : a.value, - b.size() ? b[n] : b.value ); - } - else { - for (size_t n = 0; n < out.size(); ++n) - out[n] = op.R ( - a.size() ? a[n].real() : a.value.real(), - b.size() ? b[n].real() : b.value.real() ); - } - return out; + virtual Chunk &evaluate(Chunk &a, Chunk &b) const { + Chunk &out(a.size() ? a : b); + if (operands[0].is_complex() || operands[1].is_complex()) { + for (size_t n = 0; n < out.size(); ++n) + out[n] = op.Z(a.size() ? a[n] : a.value, b.size() ? b[n] : b.value); + } else { + for (size_t n = 0; n < out.size(); ++n) + out[n] = op.R(a.size() ? a[n].real() : a.value.real(), b.size() ? b[n].real() : b.value.real()); } - + return out; + } }; +template class TernaryEvaluator : public Evaluator { +public: + TernaryEvaluator(const std::string &name, + Operation operation, + const StackEntry &operand1, + const StackEntry &operand2, + const StackEntry &operand3) + : Evaluator(name, operation.format, operation.ZtoR, operation.RtoZ), op(operation) { + operands.push_back(operand1); + operands.push_back(operand2); + operands.push_back(operand3); + } - -template -class TernaryEvaluator : public Evaluator { - public: - TernaryEvaluator (const std::string& name, Operation operation, const StackEntry& operand1, const StackEntry& operand2, const StackEntry& operand3) : - Evaluator (name, operation.format, operation.ZtoR, operation.RtoZ), - op (operation) { - operands.push_back (operand1); - operands.push_back (operand2); - operands.push_back (operand3); - } - - Operation op; - - virtual Chunk& evaluate (Chunk& a, Chunk& b, Chunk& c) const { - Chunk& out (a.size() ? a : (b.size() ? b : c)); - if (operands[0].is_complex() || operands[1].is_complex() || operands[2].is_complex()) { - for (size_t n = 0; n < out.size(); ++n) - out[n] = op.Z ( - a.size() ? a[n] : a.value, - b.size() ? b[n] : b.value, - c.size() ? c[n] : c.value ); - } - else { - for (size_t n = 0; n < out.size(); ++n) - out[n] = op.R ( - a.size() ? a[n].real() : a.value.real(), - b.size() ? b[n].real() : b.value.real(), - c.size() ? c[n].real() : c.value.real() ); - } - return out; + Operation op; + + virtual Chunk &evaluate(Chunk &a, Chunk &b, Chunk &c) const { + Chunk &out(a.size() ? a : (b.size() ? b : c)); + if (operands[0].is_complex() || operands[1].is_complex() || operands[2].is_complex()) { + for (size_t n = 0; n < out.size(); ++n) + out[n] = op.Z(a.size() ? a[n] : a.value, b.size() ? b[n] : b.value, c.size() ? c[n] : c.value); + } else { + for (size_t n = 0; n < out.size(); ++n) + out[n] = op.R(a.size() ? a[n].real() : a.value.real(), + b.size() ? b[n].real() : b.value.real(), + c.size() ? c[n].real() : c.value.real()); } - + return out; + } }; - - - - template -void unary_operation (const std::string& operation_name, vector& stack, Operation operation) -{ +void unary_operation(const std::string &operation_name, vector &stack, Operation operation) { if (stack.empty()) - throw Exception ("no operand in stack for operation \"" + operation_name + "\"!"); - StackEntry& a (stack[stack.size()-1]); + throw Exception("no operand in stack for operation \"" + operation_name + "\"!"); + StackEntry &a(stack[stack.size() - 1]); a.load(); if (a.evaluator || a.image || a.rng) { - StackEntry entry (new UnaryEvaluator (operation_name, operation, a)); + StackEntry entry(new UnaryEvaluator(operation_name, operation, a)); stack.back() = entry; - } - else { + } else { try { - a.value = ( a.value.imag() == 0.0 ? operation.R (a.value.real()) : operation.Z (a.value) ); - } - catch (...) { - throw Exception ("operation \"" + operation_name + "\" not supported for data type supplied"); + a.value = (a.value.imag() == 0.0 ? operation.R(a.value.real()) : operation.Z(a.value)); + } catch (...) { + throw Exception("operation \"" + operation_name + "\" not supported for data type supplied"); } } } - - - - template -void binary_operation (const std::string& operation_name, vector& stack, Operation operation) -{ +void binary_operation(const std::string &operation_name, vector &stack, Operation operation) { if (stack.size() < 2) - throw Exception ("not enough operands in stack for operation \"" + operation_name + "\""); - StackEntry& a (stack[stack.size()-2]); - StackEntry& b (stack[stack.size()-1]); + throw Exception("not enough operands in stack for operation \"" + operation_name + "\""); + StackEntry &a(stack[stack.size() - 2]); + StackEntry &b(stack[stack.size() - 1]); a.load(); b.load(); if (a.evaluator || a.image || a.rng || b.evaluator || b.image || b.rng) { - StackEntry entry (new BinaryEvaluator (operation_name, operation, a, b)); + StackEntry entry(new BinaryEvaluator(operation_name, operation, a, b)); stack.pop_back(); stack.back() = entry; - } - else { - a.value = ( a.value.imag() == 0.0 && b.value.imag() == 0.0 ? - operation.R (a.value.real(), b.value.real()) : - operation.Z (a.value, b.value) ); + } else { + a.value = (a.value.imag() == 0.0 && b.value.imag() == 0.0 ? operation.R(a.value.real(), b.value.real()) + : operation.Z(a.value, b.value)); stack.pop_back(); } } - - - template -void ternary_operation (const std::string& operation_name, vector& stack, Operation operation) -{ +void ternary_operation(const std::string &operation_name, vector &stack, Operation operation) { if (stack.size() < 3) - throw Exception ("not enough operands in stack for operation \"" + operation_name + "\""); - StackEntry& a (stack[stack.size()-3]); - StackEntry& b (stack[stack.size()-2]); - StackEntry& c (stack[stack.size()-1]); + throw Exception("not enough operands in stack for operation \"" + operation_name + "\""); + StackEntry &a(stack[stack.size() - 3]); + StackEntry &b(stack[stack.size() - 2]); + StackEntry &c(stack[stack.size() - 1]); a.load(); b.load(); c.load(); if (a.evaluator || a.image || a.rng || b.evaluator || b.image || b.rng || c.evaluator || c.image || c.rng) { - StackEntry entry (new TernaryEvaluator (operation_name, operation, a, b, c)); + StackEntry entry(new TernaryEvaluator(operation_name, operation, a, b, c)); stack.pop_back(); stack.pop_back(); stack.back() = entry; - } - else { - a.value = ( a.value.imag() == 0.0 && b.value.imag() == 0.0 && c.value.imag() == 0.0 ? - operation.R (a.value.real(), b.value.real(), c.value.real()) : - operation.Z (a.value, b.value, c.value) ); + } else { + a.value = (a.value.imag() == 0.0 && b.value.imag() == 0.0 && c.value.imag() == 0.0 + ? operation.R(a.value.real(), b.value.real(), c.value.real()) + : operation.Z(a.value, b.value, c.value)); stack.pop_back(); stack.pop_back(); } } - - - - /********************************************************************** MULTI-THREADED RUNNING OF OPERATIONS: **********************************************************************/ - -void get_header (const StackEntry& entry, Header& header) -{ +void get_header(const StackEntry &entry, Header &header) { if (entry.evaluator) { for (size_t n = 0; n < entry.evaluator->operands.size(); ++n) - get_header (entry.evaluator->operands[n], header); + get_header(entry.evaluator->operands[n], header); return; } @@ -720,173 +798,162 @@ void get_header (const StackEntry& entry, Header& header) if (header.ndim() < entry.image->ndim()) header.ndim() = entry.image->ndim(); - for (size_t n = 0; n < std::min (header.ndim(), entry.image->ndim()); ++n) { + for (size_t n = 0; n < std::min(header.ndim(), entry.image->ndim()); ++n) { if (header.size(n) > 1 && entry.image->size(n) > 1 && header.size(n) != entry.image->size(n)) - throw Exception ("dimensions of input images do not match - aborting"); - if (!voxel_grids_match_in_scanner_space (header, *(entry.image), 1.0e-4) && !transform_mis_match_reported) { - WARN ("header transformations of input images do not match"); + throw Exception("dimensions of input images do not match - aborting"); + if (!voxel_grids_match_in_scanner_space(header, *(entry.image), 1.0e-4) && !transform_mis_match_reported) { + WARN("header transformations of input images do not match"); transform_mis_match_reported = true; } - header.size(n) = std::max (header.size(n), entry.image->size(n)); - if (!std::isfinite (header.spacing(n))) + header.size(n) = std::max(header.size(n), entry.image->size(n)); + if (!std::isfinite(header.spacing(n))) header.spacing(n) = entry.image->spacing(n); } - header.merge_keyval (*entry.image); + header.merge_keyval(*entry.image); } +class ThreadFunctor { +public: + ThreadFunctor(const vector &inner_axes, const StackEntry &top_of_stack, Image &output_image) + : top_entry(top_of_stack), image(output_image), loop(Loop(inner_axes)) { + storage.axes = loop.axes; + storage.size.push_back(image.size(storage.axes[0])); + storage.size.push_back(image.size(storage.axes[1])); + chunk_size = image.size(storage.axes[0]) * image.size(storage.axes[1]); + allocate_storage(top_entry); + } - - - - -class ThreadFunctor { - public: - ThreadFunctor ( - const vector& inner_axes, - const StackEntry& top_of_stack, - Image& output_image) : - top_entry (top_of_stack), - image (output_image), - loop (Loop (inner_axes)) { - storage.axes = loop.axes; - storage.size.push_back (image.size(storage.axes[0])); - storage.size.push_back (image.size(storage.axes[1])); - chunk_size = image.size (storage.axes[0]) * image.size (storage.axes[1]); - allocate_storage (top_entry); - } - - void allocate_storage (const StackEntry& entry) { - if (entry.evaluator) { - for (size_t n = 0; n < entry.evaluator->operands.size(); ++n) - allocate_storage (entry.evaluator->operands[n]); - return; - } - - storage.push_back (ThreadLocalStorageItem()); - if (entry.image) { - storage.back().image.reset (new Image (*entry.image)); - storage.back().chunk.resize (chunk_size); - return; - } - else if (entry.rng) { - storage.back().chunk.resize (chunk_size); - } - else storage.back().chunk.value = entry.value; + void allocate_storage(const StackEntry &entry) { + if (entry.evaluator) { + for (size_t n = 0; n < entry.evaluator->operands.size(); ++n) + allocate_storage(entry.evaluator->operands[n]); + return; } + storage.push_back(ThreadLocalStorageItem()); + if (entry.image) { + storage.back().image.reset(new Image(*entry.image)); + storage.back().chunk.resize(chunk_size); + return; + } else if (entry.rng) { + storage.back().chunk.resize(chunk_size); + } else + storage.back().chunk.value = entry.value; + } - void operator() (const Iterator& iter) { - storage.reset (iter); - assign_pos_of (iter).to (image); - - Chunk& chunk = top_entry.evaluate (storage); - - auto value = chunk.cbegin(); - for (auto l = loop (image); l; ++l) - image.value() = *(value++); - } + void operator()(const Iterator &iter) { + storage.reset(iter); + assign_pos_of(iter).to(image); + Chunk &chunk = top_entry.evaluate(storage); + auto value = chunk.cbegin(); + for (auto l = loop(image); l; ++l) + image.value() = *(value++); + } - const StackEntry& top_entry; - Image image; - decltype (Loop (vector())) loop; - ThreadLocalStorage storage; - size_t chunk_size; + const StackEntry &top_entry; + Image image; + decltype(Loop(vector())) loop; + ThreadLocalStorage storage; + size_t chunk_size; }; - - - - -void run_operations (const vector& stack) -{ +void run_operations(const vector &stack) { Header header; - get_header (stack[0], header); + get_header(stack[0], header); if (header.ndim() == 0) { - DEBUG ("no valid images supplied - assuming calculator mode"); + DEBUG("no valid images supplied - assuming calculator mode"); if (stack.size() != 1) - throw Exception ("too many operands left on stack!"); + throw Exception("too many operands left on stack!"); - assert (!stack[0].evaluator); - assert (!stack[0].image); + assert(!stack[0].evaluator); + assert(!stack[0].image); - print (str (stack[0].value) + "\n"); + print(str(stack[0].value) + "\n"); return; } if (stack.size() == 1) - throw Exception ("output image not specified"); + throw Exception("output image not specified"); if (stack.size() > 2) - throw Exception ("too many operands left on stack!"); + throw Exception("too many operands left on stack!"); if (!stack[1].arg) - throw Exception ("output image not specified"); + throw Exception("output image not specified"); if (stack[0].is_complex()) { - header.datatype() = DataType::from_command_line (DataType::CFloat32); + header.datatype() = DataType::from_command_line(DataType::CFloat32); if (!header.datatype().is_complex()) - throw Exception ("output datatype must be complex"); - } - else header.datatype() = DataType::from_command_line (DataType::Float32); + throw Exception("output datatype must be complex"); + } else + header.datatype() = DataType::from_command_line(DataType::Float32); - auto output = Header::create (stack[1].arg, header).get_image(); + auto output = Header::create(stack[1].arg, header).get_image(); - auto loop = ThreadedLoop ("computing: " + operation_string(stack[0]), output, 0, output.ndim(), 2); + auto loop = ThreadedLoop("computing: " + operation_string(stack[0]), output, 0, output.ndim(), 2); - ThreadFunctor functor (loop.inner_axes, stack[0], output); - loop.run_outer (functor); + ThreadFunctor functor(loop.inner_axes, stack[0], output); + loop.run_outer(functor); } - - - - - - /********************************************************************** OPERATIONS BASIC FRAMEWORK: **********************************************************************/ -class OpBase { - public: - OpBase (const char* format_string, bool complex_maps_to_real = false, bool real_map_to_complex = false) : - format (format_string), - ZtoR (complex_maps_to_real), - RtoZ (real_map_to_complex) { } - const char* format; - const bool ZtoR, RtoZ; +class OpBase { +public: + OpBase(const char *format_string, bool complex_maps_to_real = false, bool real_map_to_complex = false) + : format(format_string), ZtoR(complex_maps_to_real), RtoZ(real_map_to_complex) {} + const char *format; + const bool ZtoR, RtoZ; }; -class OpUnary : public OpBase { - public: - OpUnary (const char* format_string, bool complex_maps_to_real = false, bool real_map_to_complex = false) : - OpBase (format_string, complex_maps_to_real, real_map_to_complex) { } - complex_type R (real_type v) const { throw Exception ("operation not supported!"); return v; } - complex_type Z (complex_type v) const { throw Exception ("operation not supported!"); return v; } +class OpUnary : public OpBase { +public: + OpUnary(const char *format_string, bool complex_maps_to_real = false, bool real_map_to_complex = false) + : OpBase(format_string, complex_maps_to_real, real_map_to_complex) {} + complex_type R(real_type v) const { + throw Exception("operation not supported!"); + return v; + } + complex_type Z(complex_type v) const { + throw Exception("operation not supported!"); + return v; + } }; - -class OpBinary : public OpBase { - public: - OpBinary (const char* format_string, bool complex_maps_to_real = false, bool real_map_to_complex = false) : - OpBase (format_string, complex_maps_to_real, real_map_to_complex) { } - complex_type R (real_type a, real_type b) const { throw Exception ("operation not supported!"); return a; } - complex_type Z (complex_type a, complex_type b) const { throw Exception ("operation not supported!"); return a; } +class OpBinary : public OpBase { +public: + OpBinary(const char *format_string, bool complex_maps_to_real = false, bool real_map_to_complex = false) + : OpBase(format_string, complex_maps_to_real, real_map_to_complex) {} + complex_type R(real_type a, real_type b) const { + throw Exception("operation not supported!"); + return a; + } + complex_type Z(complex_type a, complex_type b) const { + throw Exception("operation not supported!"); + return a; + } }; -class OpTernary : public OpBase { - public: - OpTernary (const char* format_string, bool complex_maps_to_real = false, bool real_map_to_complex = false) : - OpBase (format_string, complex_maps_to_real, real_map_to_complex) { } - complex_type R (real_type a, real_type b, real_type c) const { throw Exception ("operation not supported!"); return a; } - complex_type Z (complex_type a, complex_type b, complex_type c) const { throw Exception ("operation not supported!"); return a; } +class OpTernary : public OpBase { +public: + OpTernary(const char *format_string, bool complex_maps_to_real = false, bool real_map_to_complex = false) + : OpBase(format_string, complex_maps_to_real, real_map_to_complex) {} + complex_type R(real_type a, real_type b, real_type c) const { + throw Exception("operation not supported!"); + return a; + } + complex_type Z(complex_type a, complex_type b, complex_type c) const { + throw Exception("operation not supported!"); + return a; + } }; - /********************************************************************** EXPAND OPERATIONS: **********************************************************************/ @@ -894,41 +961,38 @@ class OpTernary : public OpBase { #define SECTION 2 #include "mrcalc.cpp" - - /********************************************************************** MAIN BODY OF COMMAND: **********************************************************************/ -void run () { +void run() { vector stack; for (int n = 1; n < App::argc; ++n) { - const Option* opt = match_option (App::argv[n]); + const Option *opt = match_option(App::argv[n]); if (opt) { - if (opt->is ("datatype")) ++n; - else if (opt->is ("nthreads")) ++n; - else if (opt->is ("force") || opt->is ("info") || opt->is ("debug") || opt->is ("quiet")) + if (opt->is("datatype")) + ++n; + else if (opt->is("nthreads")) + ++n; + else if (opt->is("force") || opt->is("info") || opt->is("debug") || opt->is("quiet")) continue; #define SECTION 3 #include "mrcalc.cpp" else - throw Exception (std::string ("operation \"") + opt->id + "\" not yet implemented!"); + throw Exception(std::string("operation \"") + opt->id + "\" not yet implemented!"); + } else { + stack.push_back(App::argv[n]); } - else { - stack.push_back (App::argv[n]); - } - } stack[0].load(); - run_operations (stack); + run_operations(stack); } #endif - diff --git a/cmd/mrcat.cpp b/cmd/mrcat.cpp index c7359bc1df..2c34fbcba3 100644 --- a/cmd/mrcat.cpp +++ b/cmd/mrcat.cpp @@ -14,126 +14,112 @@ * For more details, see http://www.mrtrix.org/. */ +#include "algo/loop.h" #include "command.h" +#include "dwi/gradient.h" #include "image.h" -#include "algo/loop.h" #include "phase_encoding.h" #include "progressbar.h" -#include "dwi/gradient.h" using namespace MR; using namespace App; -void usage () -{ -AUTHOR = "J-Donald Tournier (jdtournier@gmail.com) and Robert E. Smith (robert.smith@florey.edu.au)"; +void usage() { + AUTHOR = "J-Donald Tournier (jdtournier@gmail.com) and Robert E. Smith (robert.smith@florey.edu.au)"; -SYNOPSIS = "Concatenate several images into one"; + SYNOPSIS = "Concatenate several images into one"; -ARGUMENTS - + Argument ("image1", "the first input image.") - .type_image_in() + ARGUMENTS + +Argument("image1", "the first input image.").type_image_in() - + Argument ("image2", "additional input image(s).") - .type_image_in() - .allow_multiple() + + Argument("image2", "additional input image(s).").type_image_in().allow_multiple() - + Argument ("output", "the output image.") - .type_image_out (); + + Argument("output", "the output image.").type_image_out(); -EXAMPLES - + Example ("Concatenate individual 3D volumes into a single 4D image series", - "mrcat volume*.mif series.mif", - "The wildcard characters will find all images in the current working directory with names that " - "begin with \"volume\" and end with \".mif\"; the mrcat command will receive these as a list of " - "input file names, from which it will produce a 4D image where the input volumes have been " - "concatenated along axis 3 (the fourth axis; the spatial axes are 0, 1 & 2)."); + EXAMPLES + +Example("Concatenate individual 3D volumes into a single 4D image series", + "mrcat volume*.mif series.mif", + "The wildcard characters will find all images in the current working directory with names that " + "begin with \"volume\" and end with \".mif\"; the mrcat command will receive these as a list of " + "input file names, from which it will produce a 4D image where the input volumes have been " + "concatenated along axis 3 (the fourth axis; the spatial axes are 0, 1 & 2)."); -OPTIONS - + Option ("axis", - "specify axis along which concatenation should be performed. By default, " - "the program will use the last non-singleton, non-spatial axis of any of " - "the input images - in other words axis 3 or whichever axis (greater than 3) " - "of the input images has size greater than one.") - + Argument ("axis").type_integer (0) + OPTIONS + +Option("axis", + "specify axis along which concatenation should be performed. By default, " + "the program will use the last non-singleton, non-spatial axis of any of " + "the input images - in other words axis 3 or whichever axis (greater than 3) " + "of the input images has size greater than one.") + + Argument("axis").type_integer(0) - + DataType::options(); + + DataType::options(); } - - -template -void write (vector
& in, - const size_t axis, - Header& header_out) -{ - auto image_out = Image::create (header_out.name(), header_out); +template void write(vector
&in, const size_t axis, Header &header_out) { + auto image_out = Image::create(header_out.name(), header_out); size_t axis_offset = 0; for (size_t i = 0; i != in.size(); i++) { auto image_in = in[i].get_image(); - auto copy_func = [&axis, &axis_offset](decltype(image_in)& in, decltype(image_out)& out) - { - out.index (axis) = axis < in.ndim() ? in.index (axis) + axis_offset : axis_offset; + auto copy_func = [&axis, &axis_offset](decltype(image_in) &in, decltype(image_out) &out) { + out.index(axis) = axis < in.ndim() ? in.index(axis) + axis_offset : axis_offset; out.value() = in.value(); }; - ThreadedLoop ("concatenating \"" + image_in.name() + "\"", image_in, 0, std::min (image_in.ndim(), image_out.ndim())) - .run (copy_func, image_in, image_out); + ThreadedLoop( + "concatenating \"" + image_in.name() + "\"", image_in, 0, std::min(image_in.ndim(), image_out.ndim())) + .run(copy_func, image_in, image_out); if (axis < image_in.ndim()) - axis_offset += image_in.size (axis); + axis_offset += image_in.size(axis); else { ++axis_offset; - image_out.index (axis) = axis_offset; + image_out.index(axis) = axis_offset; } } } - - -void run () -{ - size_t num_images = argument.size()-1; +void run() { + size_t num_images = argument.size() - 1; vector
headers; ssize_t max_axis_nonunity = 0; for (size_t i = 0; i != num_images; ++i) { - Header H = Header::open (argument[i]); + Header H = Header::open(argument[i]); ssize_t a; - for (a = ssize_t(H.ndim())-1; a >= 0 && H.size (a) <= 1; a--); - max_axis_nonunity = std::max (max_axis_nonunity, a); - headers.push_back (std::move (H)); + for (a = ssize_t(H.ndim()) - 1; a >= 0 && H.size(a) <= 1; a--) + ; + max_axis_nonunity = std::max(max_axis_nonunity, a); + headers.push_back(std::move(H)); } - const size_t axis = get_option_value ("axis", std::max (size_t(3), size_t(std::max (ssize_t(0), max_axis_nonunity)))); + const size_t axis = get_option_value("axis", std::max(size_t(3), size_t(std::max(ssize_t(0), max_axis_nonunity)))); - Header header_out = concatenate (headers, axis, true); - header_out.name() = std::string (argument[num_images]); - header_out.datatype() = DataType::from_command_line (header_out.datatype()); + Header header_out = concatenate(headers, axis, true); + header_out.name() = std::string(argument[num_images]); + header_out.datatype() = DataType::from_command_line(header_out.datatype()); - if (header_out.intensity_offset() == 0.0 && header_out.intensity_scale() == 1.0 && !header_out.datatype().is_floating_point()) { + if (header_out.intensity_offset() == 0.0 && header_out.intensity_scale() == 1.0 && + !header_out.datatype().is_floating_point()) { switch (header_out.datatype()() & DataType::Type) { - case DataType::Bit: - case DataType::UInt8: - case DataType::UInt16: - case DataType::UInt32: - if (header_out.datatype().is_signed()) - write (headers, axis, header_out); - else - write (headers, axis, header_out); - break; - case DataType::UInt64: - if (header_out.datatype().is_signed()) - write (headers, axis, header_out); - else - write (headers, axis, header_out); - break; + case DataType::Bit: + case DataType::UInt8: + case DataType::UInt16: + case DataType::UInt32: + if (header_out.datatype().is_signed()) + write(headers, axis, header_out); + else + write(headers, axis, header_out); + break; + case DataType::UInt64: + if (header_out.datatype().is_signed()) + write(headers, axis, header_out); + else + write(headers, axis, header_out); + break; } } else { if (header_out.datatype().is_complex()) - write (headers, axis, header_out); + write(headers, axis, header_out); else - write (headers, axis, header_out); + write(headers, axis, header_out); } - } - diff --git a/cmd/mrcentroid.cpp b/cmd/mrcentroid.cpp index 39fe144c15..a1d1148850 100644 --- a/cmd/mrcentroid.cpp +++ b/cmd/mrcentroid.cpp @@ -14,7 +14,6 @@ * For more details, see http://www.mrtrix.org/. */ - #include "command.h" #include "image.h" #include "image_helpers.h" @@ -24,59 +23,52 @@ using namespace MR; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Determine the centre of mass / centre of gravity of an image"; ARGUMENTS - + Argument ("input", "the input image").type_image_in(); + +Argument("input", "the input image").type_image_in(); OPTIONS - + Option ("mask", "only include voxels within a mask in the calculation") - + Argument ("image").type_image_in() - - + Option ("voxelspace", "report image centre of mass in voxel space rather than scanner space"); + +Option("mask", "only include voxels within a mask in the calculation") + Argument("image").type_image_in() + + Option("voxelspace", "report image centre of mass in voxel space rather than scanner space"); } - typedef float value_type; - -void run () -{ - Image image = Image::open (argument[0]); +void run() { + Image image = Image::open(argument[0]); if (image.ndim() > 3) - throw Exception ("Command does not accept images with more than 3 dimensions"); + throw Exception("Command does not accept images with more than 3 dimensions"); Image mask; - auto opt = get_options ("mask"); + auto opt = get_options("mask"); if (opt.size()) { - mask = Image::open (opt[0][0]); - check_dimensions (image, mask); + mask = Image::open(opt[0][0]); + check_dimensions(image, mask); } - Eigen::Vector3d com (0.0, 0.0, 0.0); + Eigen::Vector3d com(0.0, 0.0, 0.0); default_type mass = 0.0; if (mask.valid()) { - for (auto l = Loop(image) (image, mask); l; ++l) { + for (auto l = Loop(image)(image, mask); l; ++l) { if (mask.value()) { - com += Eigen::Vector3d (image.index(0), image.index(1), image.index(2)) * image.value(); + com += Eigen::Vector3d(image.index(0), image.index(1), image.index(2)) * image.value(); mass += image.value(); } } } else { - for (auto l = Loop(image) (image); l; ++l) { - com += Eigen::Vector3d (image.index(0), image.index(1), image.index(2)) * image.value(); + for (auto l = Loop(image)(image); l; ++l) { + com += Eigen::Vector3d(image.index(0), image.index(1), image.index(2)) * image.value(); mass += image.value(); } } com /= mass; - if (!get_options ("voxelspace").size()) + if (!get_options("voxelspace").size()) com = image.transform() * com; std::cout << com.transpose(); diff --git a/cmd/mrcheckerboardmask.cpp b/cmd/mrcheckerboardmask.cpp index 42a546009f..13b8c09369 100644 --- a/cmd/mrcheckerboardmask.cpp +++ b/cmd/mrcheckerboardmask.cpp @@ -16,70 +16,65 @@ #include +#include "algo/loop.h" #include "command.h" #include "image.h" #include "image_helpers.h" -#include "algo/loop.h" using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "Max Pietsch (maximilian.pietsch@kcl.ac.uk)"; SYNOPSIS = "Create bitwise checkerboard image"; ARGUMENTS - + Argument ("input", "the input image to be used as a template.").type_image_in () - + Argument ("output", "the output binary image mask.").type_image_out (); + +Argument("input", "the input image to be used as a template.").type_image_in() + + Argument("output", "the output binary image mask.").type_image_out(); OPTIONS - + Option ("tiles", "specify the number of tiles in any direction") - + Argument ("value").type_integer() + +Option("tiles", "specify the number of tiles in any direction") + Argument("value").type_integer() - + Option ("invert", "invert output binary mask.") + + Option("invert", "invert output binary mask.") - + Option ("nan", "use NaN as the output zero value."); + + Option("nan", "use NaN as the output zero value."); } - -void run () -{ +void run() { size_t ntiles = 5; - auto opt = get_options ("tiles"); + auto opt = get_options("tiles"); if (opt.size()) { ntiles = opt[0][0]; } - bool invert = get_options ("invert").size(); - const bool use_NaN = get_options ("nan").size(); + bool invert = get_options("invert").size(); + const bool use_NaN = get_options("nan").size(); - auto in = Image::open (argument[0]); - check_3D_nonunity (in); + auto in = Image::open(argument[0]); + check_3D_nonunity(in); - size_t patchwidth_x = ceil((float) in.size(0) / ntiles); - size_t patchwidth_y = ceil((float) in.size(1) / ntiles); - size_t patchwidth_z = ceil((float) in.size(2) / ntiles); + size_t patchwidth_x = ceil((float)in.size(0) / ntiles); + size_t patchwidth_y = ceil((float)in.size(1) / ntiles); + size_t patchwidth_z = ceil((float)in.size(2) / ntiles); - Header header_out (in); + Header header_out(in); header_out.datatype() = use_NaN ? DataType::Float32 : DataType::Bit; - auto out = Image::create (argument[1], header_out); + auto out = Image::create(argument[1], header_out); float zero = use_NaN ? NAN : 0.0; - float one = 1.0; - if (invert) std::swap (zero, one); + float one = 1.0; + if (invert) + std::swap(zero, one); - for (auto l = Loop(in) (in, out); l; ++l) { + for (auto l = Loop(in)(in, out); l; ++l) { const size_t x = in.index(0); const size_t y = in.index(1); const size_t z = in.index(2); - size_t xpatch = (x-(x%patchwidth_x))/patchwidth_x; - size_t ypatch = (y-(y%patchwidth_y))/patchwidth_y; - size_t zpatch = (z-(z%patchwidth_z))/patchwidth_z; - out.value() = ( - (xpatch%2 + ypatch%2+ zpatch%2)%2==0) ? one : zero; + size_t xpatch = (x - (x % patchwidth_x)) / patchwidth_x; + size_t ypatch = (y - (y % patchwidth_y)) / patchwidth_y; + size_t zpatch = (z - (z % patchwidth_z)) / patchwidth_z; + out.value() = ((xpatch % 2 + ypatch % 2 + zpatch % 2) % 2 == 0) ? one : zero; } - } diff --git a/cmd/mrclusterstats.cpp b/cmd/mrclusterstats.cpp index 63579f17ca..6ee2be9498 100644 --- a/cmd/mrclusterstats.cpp +++ b/cmd/mrclusterstats.cpp @@ -33,7 +33,6 @@ #include "stats/permtest.h" #include "stats/tfce.h" - using namespace MR; using namespace App; using namespace MR::Math::Stats; @@ -42,83 +41,71 @@ using namespace MR::Math::Stats::GLM; using MR::Math::Stats::index_type; using Stats::PermTest::count_matrix_type; - #define DEFAULT_TFCE_DH 0.1 #define DEFAULT_TFCE_H 2.0 #define DEFAULT_TFCE_E 0.5 #define DEFAULT_EMPIRICAL_SKEW 1.0 - -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Voxel-based analysis using permutation testing and threshold-free cluster enhancement"; DESCRIPTION - + Math::Stats::GLM::column_ones_description; + +Math::Stats::GLM::column_ones_description; REFERENCES - + "* If not using the -threshold command-line option:\n" + +"* If not using the -threshold command-line option:\n" "Smith, S. M. & Nichols, T. E. " - "Threshold-free cluster enhancement: Addressing problems of smoothing, threshold dependence and localisation in cluster inference. " + "Threshold-free cluster enhancement: Addressing problems of smoothing, threshold dependence and localisation in " + "cluster inference. " "NeuroImage, 2009, 44, 83-98" - + "* If using the -nonstationary option:\n" - "Salimi-Khorshidi, G. Smith, S.M. Nichols, T.E. Adjusting the effect of nonstationarity in cluster-based and TFCE inference. " - "Neuroimage, 2011, 54(3), 2006-19"; - + + "* If using the -nonstationary option:\n" + "Salimi-Khorshidi, G. Smith, S.M. Nichols, T.E. Adjusting the effect of nonstationarity in cluster-based and " + "TFCE inference. " + "Neuroimage, 2011, 54(3), 2006-19"; ARGUMENTS - + Argument ("input", "a text file containing the file names of the input images, one file per line").type_file_in() - - + Argument ("design", "the design matrix").type_file_in() + +Argument("input", "a text file containing the file names of the input images, one file per line").type_file_in() - + Argument ("contrast", "the contrast matrix").type_file_in() + + Argument("design", "the design matrix").type_file_in() - + Argument ("mask", "a mask used to define voxels included in the analysis.").type_image_in() + + Argument("contrast", "the contrast matrix").type_file_in() - + Argument ("output", "the filename prefix for all output.").type_text(); + + Argument("mask", "a mask used to define voxels included in the analysis.").type_image_in() + + Argument("output", "the filename prefix for all output.").type_text(); OPTIONS - + Math::Stats::shuffle_options (true, DEFAULT_EMPIRICAL_SKEW) - - + Stats::TFCE::Options (DEFAULT_TFCE_DH, DEFAULT_TFCE_E, DEFAULT_TFCE_H) + +Math::Stats::shuffle_options(true, DEFAULT_EMPIRICAL_SKEW) - + Math::Stats::GLM::glm_options ("voxel") + + Stats::TFCE::Options(DEFAULT_TFCE_DH, DEFAULT_TFCE_E, DEFAULT_TFCE_H) - + OptionGroup ("Additional options for mrclusterstats") + + Math::Stats::GLM::glm_options("voxel") - + Option ("threshold", "the cluster-forming threshold to use for a standard cluster-based analysis. " - "This disables TFCE, which is the default otherwise.") - + Argument ("value").type_float (1.0e-6) + + OptionGroup("Additional options for mrclusterstats") - + Option ("connectivity", "use 26-voxel-neighbourhood connectivity (Default: 6)"); + + Option("threshold", + "the cluster-forming threshold to use for a standard cluster-based analysis. " + "This disables TFCE, which is the default otherwise.") + + Argument("value").type_float(1.0e-6) + + Option("connectivity", "use 26-voxel-neighbourhood connectivity (Default: 6)"); } - - using value_type = Stats::TFCE::value_type; - - template -void write_output (const VectorType& data, - const Voxel2Vector& v2v, - const std::string& path, - const Header& header) { - auto image = Image::create (path, header); +void write_output(const VectorType &data, const Voxel2Vector &v2v, const std::string &path, const Header &header) { + auto image = Image::create(path, header); for (index_type i = 0; i != v2v.size(); i++) { - assign_pos_of (v2v[i]).to (image); + assign_pos_of(v2v[i]).to(image); image.value() = data[i]; } } - - // Define data importer class that will obtain voxel data for a // specific subject based on the string path to the image file for // that subject @@ -132,147 +119,141 @@ void write_output (const VectorType& data, // any modification of the class constructor (since these data // are initialised in the CohortDataImport class). // -class SubjectVoxelImport : public SubjectDataImportBase -{ - public: - SubjectVoxelImport (const std::string& path) : - SubjectDataImportBase (path), - H (Header::open (path)), - data (H.get_image()) { } - - virtual ~SubjectVoxelImport() { } - - void operator() (matrix_type::RowXpr row) const override - { - assert (v2v); - Image temp (data); // For thread-safety - for (index_type i = 0; i != size(); ++i) { - assign_pos_of ((*v2v)[i]).to (temp); - row[i] = temp.value(); - } +class SubjectVoxelImport : public SubjectDataImportBase { +public: + SubjectVoxelImport(const std::string &path) + : SubjectDataImportBase(path), H(Header::open(path)), data(H.get_image()) {} + + virtual ~SubjectVoxelImport() {} + + void operator()(matrix_type::RowXpr row) const override { + assert(v2v); + Image temp(data); // For thread-safety + for (index_type i = 0; i != size(); ++i) { + assign_pos_of((*v2v)[i]).to(temp); + row[i] = temp.value(); } + } - default_type operator[] (const index_type index) const override - { - assert (v2v); - Image temp (data); // For thread-safety - assign_pos_of ((*v2v)[index]).to (temp); - assert (!is_out_of_bounds (temp)); - return temp.value(); - } + default_type operator[](const index_type index) const override { + assert(v2v); + Image temp(data); // For thread-safety + assign_pos_of((*v2v)[index]).to(temp); + assert(!is_out_of_bounds(temp)); + return temp.value(); + } - index_type size() const override { assert (v2v); return v2v->size(); } + index_type size() const override { + assert(v2v); + return v2v->size(); + } - const Header& header() const { return H; } + const Header &header() const { return H; } - static void set_mapping (std::shared_ptr& ptr) { - v2v = ptr; - } + static void set_mapping(std::shared_ptr &ptr) { v2v = ptr; } - private: - Header H; - const Image data; - - static std::shared_ptr v2v; +private: + Header H; + const Image data; + static std::shared_ptr v2v; }; std::shared_ptr SubjectVoxelImport::v2v = nullptr; - - - void run() { - const value_type cluster_forming_threshold = get_option_value ("threshold", NaN); - const value_type tfce_dh = get_option_value ("tfce_dh", DEFAULT_TFCE_DH); - const value_type tfce_H = get_option_value ("tfce_h", DEFAULT_TFCE_H); - const value_type tfce_E = get_option_value ("tfce_e", DEFAULT_TFCE_E); - const bool use_tfce = !std::isfinite (cluster_forming_threshold); + const value_type cluster_forming_threshold = get_option_value("threshold", NaN); + const value_type tfce_dh = get_option_value("tfce_dh", DEFAULT_TFCE_DH); + const value_type tfce_H = get_option_value("tfce_h", DEFAULT_TFCE_H); + const value_type tfce_E = get_option_value("tfce_e", DEFAULT_TFCE_E); + const bool use_tfce = !std::isfinite(cluster_forming_threshold); const bool do_26_connectivity = get_options("connectivity").size(); - const bool do_nonstationarity_adjustment = get_options ("nonstationarity").size(); - const default_type empirical_skew = get_option_value ("skew_nonstationarity", DEFAULT_EMPIRICAL_SKEW); + const bool do_nonstationarity_adjustment = get_options("nonstationarity").size(); + const default_type empirical_skew = get_option_value("skew_nonstationarity", DEFAULT_EMPIRICAL_SKEW); // Load analysis mask and compute adjacency - auto mask_header = Header::open (argument[3]); - check_effective_dimensionality (mask_header, 3); + auto mask_header = Header::open(argument[3]); + check_effective_dimensionality(mask_header, 3); auto mask_image = mask_header.get_image(); - std::shared_ptr v2v = make_shared (mask_image, mask_header); - SubjectVoxelImport::set_mapping (v2v); + std::shared_ptr v2v = make_shared(mask_image, mask_header); + SubjectVoxelImport::set_mapping(v2v); Filter::Connector connector; - connector.adjacency.set_26_adjacency (do_26_connectivity); - connector.adjacency.initialise (mask_header, *v2v); + connector.adjacency.set_26_adjacency(do_26_connectivity); + connector.adjacency.initialise(mask_header, *v2v); const index_type num_voxels = v2v->size(); // Read file names and check files exist CohortDataImport importer; - importer.initialise (argument[0]); + importer.initialise(argument[0]); for (index_type i = 0; i != importer.size(); ++i) { - if (!dimensions_match (dynamic_cast(importer[i].get())->header(), mask_header)) - throw Exception ("Image file \"" + importer[i]->name() + "\" does not match analysis mask"); + if (!dimensions_match(dynamic_cast(importer[i].get())->header(), mask_header)) + throw Exception("Image file \"" + importer[i]->name() + "\" does not match analysis mask"); } - CONSOLE ("Number of inputs: " + str(importer.size())); + CONSOLE("Number of inputs: " + str(importer.size())); // Load design matrix - const matrix_type design = File::Matrix::load_matrix (argument[1]); + const matrix_type design = File::Matrix::load_matrix(argument[1]); if (index_type(design.rows()) != importer.size()) - throw Exception ("Number of input files does not match number of rows in design matrix"); + throw Exception("Number of input files does not match number of rows in design matrix"); // Before validating the contrast matrix, we first need to see if there are any // additional design matrix columns coming from voxel-wise subject data // TODO Functionalise this vector extra_columns; bool nans_in_columns = false; - auto opt = get_options ("column"); + auto opt = get_options("column"); for (size_t i = 0; i != opt.size(); ++i) { - extra_columns.push_back (CohortDataImport()); - extra_columns[i].initialise (opt[i][0]); + extra_columns.push_back(CohortDataImport()); + extra_columns[i].initialise(opt[i][0]); if (!extra_columns[i].allFinite()) nans_in_columns = true; } const index_type num_factors = design.cols() + extra_columns.size(); - CONSOLE ("Number of factors: " + str(num_factors)); + CONSOLE("Number of factors: " + str(num_factors)); if (extra_columns.size()) { - CONSOLE ("Number of element-wise design matrix columns: " + str(extra_columns.size())); + CONSOLE("Number of element-wise design matrix columns: " + str(extra_columns.size())); if (nans_in_columns) - CONSOLE ("Non-finite values detected in element-wise design matrix columns; individual rows will be removed from voxel-wise design matrices accordingly"); + CONSOLE("Non-finite values detected in element-wise design matrix columns; individual rows will be removed from " + "voxel-wise design matrices accordingly"); } - check_design (design, extra_columns.size()); + check_design(design, extra_columns.size()); // Load variance groups - auto variance_groups = GLM::load_variance_groups (design.rows()); - const index_type num_vgs = variance_groups.size() ? variance_groups.maxCoeff()+1 : 1; + auto variance_groups = GLM::load_variance_groups(design.rows()); + const index_type num_vgs = variance_groups.size() ? variance_groups.maxCoeff() + 1 : 1; if (num_vgs > 1) - CONSOLE ("Number of variance groups: " + str(num_vgs)); + CONSOLE("Number of variance groups: " + str(num_vgs)); // Load hypotheses - const vector hypotheses = Math::Stats::GLM::load_hypotheses (argument[2]); + const vector hypotheses = Math::Stats::GLM::load_hypotheses(argument[2]); const index_type num_hypotheses = hypotheses.size(); if (hypotheses[0].cols() != num_factors) - throw Exception ("The number of columns in the contrast matrix (" + str(hypotheses[0].cols()) + ")" - + " does not equal the number of columns in the design matrix (" + str(design.cols()) + ")" - + (extra_columns.size() ? " (taking into account the " + str(extra_columns.size()) + " uses of -column)" : "")); - CONSOLE ("Number of hypotheses: " + str(num_hypotheses)); + throw Exception( + "The number of columns in the contrast matrix (" + str(hypotheses[0].cols()) + ")" + + " does not equal the number of columns in the design matrix (" + str(design.cols()) + ")" + + (extra_columns.size() ? " (taking into account the " + str(extra_columns.size()) + " uses of -column)" : "")); + CONSOLE("Number of hypotheses: " + str(num_hypotheses)); - matrix_type data (importer.size(), num_voxels); + matrix_type data(importer.size(), num_voxels); { // Load images - ProgressBar progress ("loading input images", importer.size()); + ProgressBar progress("loading input images", importer.size()); for (index_type subject = 0; subject < importer.size(); subject++) { - (*importer[subject]) (data.row (subject)); + (*importer[subject])(data.row(subject)); progress++; } } const bool nans_in_data = !data.allFinite(); if (nans_in_data) { - INFO ("Non-finite values present in data; rows will be removed from voxel-wise design matrices accordingly"); + INFO("Non-finite values present in data; rows will be removed from voxel-wise design matrices accordingly"); if (!extra_columns.size()) { - INFO ("(Note that this will result in slower execution than if such values were not present)"); + INFO("(Note that this will result in slower execution than if such values were not present)"); } } - Header output_header (mask_header); + Header output_header(mask_header); output_header.datatype() = DataType::Float32; - //output_header.keyval()["num permutations"] = str(num_perms); + // output_header.keyval()["num permutations"] = str(num_perms); output_header.keyval()["26 connectivity"] = str(do_26_connectivity); output_header.keyval()["nonstationary adjustment"] = str(do_nonstationarity_adjustment); if (use_tfce) { @@ -283,46 +264,47 @@ void run() { output_header.keyval()["threshold"] = str(cluster_forming_threshold); } - const std::string prefix (argument[4]); + const std::string prefix(argument[4]); // Only add contrast matrix row number to image outputs if there's more than one hypothesis - auto postfix = [&] (const index_type i) { return (num_hypotheses > 1) ? ("_" + hypotheses[i].name()) : ""; }; + auto postfix = [&](const index_type i) { return (num_hypotheses > 1) ? ("_" + hypotheses[i].name()) : ""; }; { - matrix_type betas (num_factors, num_voxels); - matrix_type abs_effect_size (num_voxels, num_hypotheses); - matrix_type std_effect_size (num_voxels, num_hypotheses); - matrix_type stdev (num_vgs, num_voxels); - vector_type cond (num_voxels); + matrix_type betas(num_factors, num_voxels); + matrix_type abs_effect_size(num_voxels, num_hypotheses); + matrix_type std_effect_size(num_voxels, num_hypotheses); + matrix_type stdev(num_vgs, num_voxels); + vector_type cond(num_voxels); - Math::Stats::GLM::all_stats (data, design, extra_columns, hypotheses, variance_groups, - cond, betas, abs_effect_size, std_effect_size, stdev); + Math::Stats::GLM::all_stats( + data, design, extra_columns, hypotheses, variance_groups, cond, betas, abs_effect_size, std_effect_size, stdev); - ProgressBar progress ("Outputting beta coefficients, effect size and standard deviation", num_factors + (2 * num_hypotheses) + num_vgs + (nans_in_data || extra_columns.size() ? 1 : 0)); + ProgressBar progress("Outputting beta coefficients, effect size and standard deviation", + num_factors + (2 * num_hypotheses) + num_vgs + (nans_in_data || extra_columns.size() ? 1 : 0)); for (index_type i = 0; i != num_factors; ++i) { - write_output (betas.row(i), *v2v, prefix + "beta" + str(i) + ".mif", output_header); + write_output(betas.row(i), *v2v, prefix + "beta" + str(i) + ".mif", output_header); ++progress; } for (index_type i = 0; i != num_hypotheses; ++i) { if (!hypotheses[i].is_F()) { - write_output (abs_effect_size.col(i), *v2v, prefix + "abs_effect" + postfix(i) + ".mif", output_header); + write_output(abs_effect_size.col(i), *v2v, prefix + "abs_effect" + postfix(i) + ".mif", output_header); ++progress; if (num_vgs == 1) - write_output (std_effect_size.col(i), *v2v, prefix + "std_effect" + postfix(i) + ".mif", output_header); + write_output(std_effect_size.col(i), *v2v, prefix + "std_effect" + postfix(i) + ".mif", output_header); } else { ++progress; } ++progress; } if (nans_in_data || extra_columns.size()) { - write_output (cond, *v2v, prefix + "cond.mif", output_header); + write_output(cond, *v2v, prefix + "cond.mif", output_header); ++progress; } if (num_vgs == 1) { - write_output (stdev.row(0), *v2v, prefix + "std_dev.mif", output_header); + write_output(stdev.row(0), *v2v, prefix + "std_dev.mif", output_header); } else { for (index_type i = 0; i != num_vgs; ++i) { - write_output (stdev.row(i), *v2v, prefix + "std_dev" + str(i) + ".mif", output_header); + write_output(stdev.row(i), *v2v, prefix + "std_dev" + str(i) + ".mif", output_header); ++progress; } } @@ -332,45 +314,56 @@ void run() { std::shared_ptr glm_test; if (extra_columns.size() || nans_in_data) { if (variance_groups.size()) - glm_test.reset (new GLM::TestVariableHeteroscedastic (extra_columns, data, design, hypotheses, variance_groups, nans_in_data, nans_in_columns)); + glm_test.reset(new GLM::TestVariableHeteroscedastic( + extra_columns, data, design, hypotheses, variance_groups, nans_in_data, nans_in_columns)); else - glm_test.reset (new GLM::TestVariableHomoscedastic (extra_columns, data, design, hypotheses, nans_in_data, nans_in_columns)); + glm_test.reset( + new GLM::TestVariableHomoscedastic(extra_columns, data, design, hypotheses, nans_in_data, nans_in_columns)); } else { if (variance_groups.size()) - glm_test.reset (new GLM::TestFixedHeteroscedastic (data, design, hypotheses, variance_groups)); + glm_test.reset(new GLM::TestFixedHeteroscedastic(data, design, hypotheses, variance_groups)); else - glm_test.reset (new GLM::TestFixedHomoscedastic (data, design, hypotheses)); + glm_test.reset(new GLM::TestFixedHomoscedastic(data, design, hypotheses)); } std::shared_ptr enhancer; if (use_tfce) { - std::shared_ptr base (new Stats::Cluster::ClusterSize (connector, cluster_forming_threshold)); - enhancer.reset (new Stats::TFCE::Wrapper (base, tfce_dh, tfce_E, tfce_H)); + std::shared_ptr base( + new Stats::Cluster::ClusterSize(connector, cluster_forming_threshold)); + enhancer.reset(new Stats::TFCE::Wrapper(base, tfce_dh, tfce_E, tfce_H)); } else { - enhancer.reset (new Stats::Cluster::ClusterSize (connector, cluster_forming_threshold)); + enhancer.reset(new Stats::Cluster::ClusterSize(connector, cluster_forming_threshold)); } matrix_type empirical_enhanced_statistic; if (do_nonstationarity_adjustment) { if (!use_tfce) - throw Exception ("Nonstationarity adjustment is not currently implemented for threshold-based cluster analysis"); - Stats::PermTest::precompute_empirical_stat (glm_test, enhancer, empirical_skew, empirical_enhanced_statistic); + throw Exception("Nonstationarity adjustment is not currently implemented for threshold-based cluster analysis"); + Stats::PermTest::precompute_empirical_stat(glm_test, enhancer, empirical_skew, empirical_enhanced_statistic); for (index_type i = 0; i != num_hypotheses; ++i) - write_output (empirical_enhanced_statistic.col(i), *v2v, prefix + "empirical" + postfix(i) + ".mif", output_header); + write_output( + empirical_enhanced_statistic.col(i), *v2v, prefix + "empirical" + postfix(i) + ".mif", output_header); } // Precompute statistic value and enhanced statistic for the default permutation matrix_type default_statistic, default_zstat, default_enhanced; - Stats::PermTest::precompute_default_permutation (glm_test, enhancer, empirical_enhanced_statistic, default_statistic, default_zstat, default_enhanced); + Stats::PermTest::precompute_default_permutation( + glm_test, enhancer, empirical_enhanced_statistic, default_statistic, default_zstat, default_enhanced); for (index_type i = 0; i != num_hypotheses; ++i) { - write_output (default_statistic.col (i), *v2v, prefix + (hypotheses[i].is_F() ? "F" : "t") + "value" + postfix(i) + ".mif", output_header); - write_output (default_zstat .col (i), *v2v, prefix + "Zstat" + postfix(i) + ".mif", output_header); - write_output (default_enhanced .col (i), *v2v, prefix + (use_tfce ? "tfce" : "clustersize") + postfix(i) + ".mif", output_header); + write_output(default_statistic.col(i), + *v2v, + prefix + (hypotheses[i].is_F() ? "F" : "t") + "value" + postfix(i) + ".mif", + output_header); + write_output(default_zstat.col(i), *v2v, prefix + "Zstat" + postfix(i) + ".mif", output_header); + write_output(default_enhanced.col(i), + *v2v, + prefix + (use_tfce ? "tfce" : "clustersize") + postfix(i) + ".mif", + output_header); } - if (!get_options ("notest").size()) { + if (!get_options("notest").size()) { - const bool fwe_strong = get_options ("strong").size(); + const bool fwe_strong = get_options("strong").size(); if (fwe_strong && num_hypotheses == 1) { WARN("Option -strong has no effect when testing a single hypothesis only"); } @@ -378,31 +371,37 @@ void run() { matrix_type null_distribution, uncorrected_pvalue; count_matrix_type null_contributions; - Stats::PermTest::run_permutations (glm_test, enhancer, empirical_enhanced_statistic, default_enhanced, fwe_strong, - null_distribution, null_contributions, uncorrected_pvalue); + Stats::PermTest::run_permutations(glm_test, + enhancer, + empirical_enhanced_statistic, + default_enhanced, + fwe_strong, + null_distribution, + null_contributions, + uncorrected_pvalue); - ProgressBar progress ("Outputting final results", (fwe_strong ? 1 : num_hypotheses) + 1 + 3*num_hypotheses); + ProgressBar progress("Outputting final results", (fwe_strong ? 1 : num_hypotheses) + 1 + 3 * num_hypotheses); if (fwe_strong) { - File::Matrix::save_vector (null_distribution.col(0), prefix + "null_dist.txt"); + File::Matrix::save_vector(null_distribution.col(0), prefix + "null_dist.txt"); ++progress; } else { for (index_type i = 0; i != num_hypotheses; ++i) { - File::Matrix::save_vector (null_distribution.col(i), prefix + "null_dist" + postfix(i) + ".txt"); + File::Matrix::save_vector(null_distribution.col(i), prefix + "null_dist" + postfix(i) + ".txt"); ++progress; } } - const matrix_type fwe_pvalue_output = MR::Math::Stats::fwe_pvalue (null_distribution, default_enhanced); + const matrix_type fwe_pvalue_output = MR::Math::Stats::fwe_pvalue(null_distribution, default_enhanced); ++progress; for (index_type i = 0; i != num_hypotheses; ++i) { - write_output (fwe_pvalue_output.col(i), *v2v, prefix + "fwe_1mpvalue" + postfix(i) + ".mif", output_header); + write_output(fwe_pvalue_output.col(i), *v2v, prefix + "fwe_1mpvalue" + postfix(i) + ".mif", output_header); ++progress; - write_output (uncorrected_pvalue.col(i), *v2v, prefix + "uncorrected_1mpvalue" + postfix(i) + ".mif", output_header); + write_output( + uncorrected_pvalue.col(i), *v2v, prefix + "uncorrected_1mpvalue" + postfix(i) + ".mif", output_header); ++progress; - write_output (null_contributions.col(i), *v2v, prefix + "null_contributions" + postfix(i) + ".mif", output_header); + write_output(null_contributions.col(i), *v2v, prefix + "null_contributions" + postfix(i) + ".mif", output_header); ++progress; } - } } diff --git a/cmd/mrcolour.cpp b/cmd/mrcolour.cpp index e36636e0f1..a9143f6d12 100644 --- a/cmd/mrcolour.cpp +++ b/cmd/mrcolour.cpp @@ -29,139 +29,130 @@ using namespace MR; using namespace App; - vector colourmap_choices_std; -vector colourmap_choices_cstr; +vector colourmap_choices_cstr; -void usage () -{ +void usage() { - const ColourMap::Entry* entry = ColourMap::maps; + const ColourMap::Entry *entry = ColourMap::maps; do { if (strcmp(entry->name, "Complex")) - colourmap_choices_std.push_back (lowercase (entry->name)); + colourmap_choices_std.push_back(lowercase(entry->name)); ++entry; } while (entry->name); - colourmap_choices_cstr.reserve (colourmap_choices_std.size() + 1); - for (const auto& s : colourmap_choices_std) - colourmap_choices_cstr.push_back (s.c_str()); - colourmap_choices_cstr.push_back (nullptr); + colourmap_choices_cstr.reserve(colourmap_choices_std.size() + 1); + for (const auto &s : colourmap_choices_std) + colourmap_choices_cstr.push_back(s.c_str()); + colourmap_choices_cstr.push_back(nullptr); AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Apply a colour map to an image"; DESCRIPTION - + "Under typical usage, this command will receive as input ad 3D greyscale image, and " - "output a 4D image with 3 volumes corresponding to red-green-blue components; " - "other use cases are possible, and are described in more detail below." + +"Under typical usage, this command will receive as input ad 3D greyscale image, and " + "output a 4D image with 3 volumes corresponding to red-green-blue components; " + "other use cases are possible, and are described in more detail below." - + "By default, the command will automatically determine the maximum and minimum " - "intensities of the input image, and use that information to set the upper and " - "lower bounds of the applied colourmap. This behaviour can be overridden by manually " - "specifying these bounds using the -upper and -lower options respectively."; + + "By default, the command will automatically determine the maximum and minimum " + "intensities of the input image, and use that information to set the upper and " + "lower bounds of the applied colourmap. This behaviour can be overridden by manually " + "specifying these bounds using the -upper and -lower options respectively."; ARGUMENTS - + Argument ("input", "the input image").type_image_in() - + Argument ("map", "the colourmap to apply; choices are: " + join(colourmap_choices_std, ",")).type_choice (colourmap_choices_cstr.data()) - + Argument ("output", "the output image").type_image_out(); + +Argument("input", "the input image").type_image_in() + + Argument("map", "the colourmap to apply; choices are: " + join(colourmap_choices_std, ",")) + .type_choice(colourmap_choices_cstr.data()) + + Argument("output", "the output image").type_image_out(); OPTIONS - + Option ("upper", "manually set the upper intensity of the colour mapping") - + Argument ("value").type_float() - - + Option ("lower", "manually set the lower intensity of the colour mapping") - + Argument ("value").type_float() + +Option("upper", "manually set the upper intensity of the colour mapping") + Argument("value").type_float() - + Option ("colour", "set the target colour for use of the 'colour' map (three comma-separated floating-point values)") - + Argument ("values").type_sequence_float(); + + Option("lower", "manually set the lower intensity of the colour mapping") + Argument("value").type_float() + + Option("colour", + "set the target colour for use of the 'colour' map (three comma-separated floating-point values)") + + Argument("values").type_sequence_float(); } - - - - -void run () -{ - Header H_in = Header::open (argument[0]); +void run() { + Header H_in = Header::open(argument[0]); const ColourMap::Entry colourmap = ColourMap::maps[argument[1]]; - Eigen::Vector3d fixed_colour (NaN, NaN, NaN); + Eigen::Vector3d fixed_colour(NaN, NaN, NaN); if (colourmap.is_colour) { if (!(H_in.ndim() == 3 || (H_in.ndim() == 4 && H_in.size(3) == 1))) - throw Exception ("For applying a fixed colour, command expects a 3D image as input"); - auto opt = get_options ("colour"); + throw Exception("For applying a fixed colour, command expects a 3D image as input"); + auto opt = get_options("colour"); if (!opt.size()) - throw Exception ("For \'colour\' colourmap, target colour must be specified using the -colour option"); - const auto values = parse_floats (opt[0][0]); + throw Exception("For \'colour\' colourmap, target colour must be specified using the -colour option"); + const auto values = parse_floats(opt[0][0]); if (values.size() != 3) - throw Exception ("Target colour must be specified as a comma-separated list of three values"); - fixed_colour = Eigen::Vector3d (values.data()); + throw Exception("Target colour must be specified as a comma-separated list of three values"); + fixed_colour = Eigen::Vector3d(values.data()); if (fixed_colour.minCoeff() < 0.0) - throw Exception ("Values for fixed colour provided via -colour option cannot be negative"); + throw Exception("Values for fixed colour provided via -colour option cannot be negative"); } else if (colourmap.is_rgb) { if (!(H_in.ndim() == 4 && H_in.size(3) == 3)) - throw Exception ("\'rgb\' colourmap only applies to 4D images with 3 volumes"); - if (get_options ("lower").size()) { - WARN ("Option -lower ignored: not compatible with \'rgb\' colourmap (a minimum of 0.0 is assumed)"); + throw Exception("\'rgb\' colourmap only applies to 4D images with 3 volumes"); + if (get_options("lower").size()) { + WARN("Option -lower ignored: not compatible with \'rgb\' colourmap (a minimum of 0.0 is assumed)"); } } else { if (!(H_in.ndim() == 3 || (H_in.ndim() == 4 && H_in.size(3) == 1))) - throw Exception ("For standard colour maps, command expects a 3D image as input"); - if (get_options ("colour").size()) { - WARN ("Option -colour ignored: only applies if \'colour\' colourmap is used"); + throw Exception("For standard colour maps, command expects a 3D image as input"); + if (get_options("colour").size()) { + WARN("Option -colour ignored: only applies if \'colour\' colourmap is used"); } } auto in = H_in.get_image(); - float lower = colourmap.is_rgb ? 0.0 : get_option_value ("lower", NaN); - float upper = get_option_value ("upper", NaN); - if (!std::isfinite (lower) || !std::isfinite (upper)) { + float lower = colourmap.is_rgb ? 0.0 : get_option_value("lower", NaN); + float upper = get_option_value("upper", NaN); + if (!std::isfinite(lower) || !std::isfinite(upper)) { float image_min = NaN, image_max = NaN; - min_max (in, image_min, image_max); + min_max(in, image_min, image_max); if (colourmap.is_rgb) { // RGB - image_max = std::max (MR::abs (image_min), MR::abs (image_max)); + image_max = std::max(MR::abs(image_min), MR::abs(image_max)); } else { - if (!std::isfinite (lower)) { - if (!std::isfinite (image_min)) - throw Exception ("Unable to determine minimum value from image"); + if (!std::isfinite(lower)) { + if (!std::isfinite(image_min)) + throw Exception("Unable to determine minimum value from image"); lower = image_min; } } - if (!std::isfinite (upper)) { - if (!std::isfinite (image_max)) - throw Exception ("Unable to determine maximum value from image"); + if (!std::isfinite(upper)) { + if (!std::isfinite(image_max)) + throw Exception("Unable to determine maximum value from image"); upper = image_max; } } const float multiplier = 1.0f / (upper - lower); - auto scale = [&] (const float value) { return std::max (0.0f, std::min (1.0f, multiplier * (value - lower))); }; + auto scale = [&](const float value) { return std::max(0.0f, std::min(1.0f, multiplier * (value - lower))); }; - Header H_out (H_in); + Header H_out(H_in); H_out.ndim() = 4; H_out.size(3) = 3; - Stride::set (H_out, Stride::contiguous_along_axis (3, H_out)); + Stride::set(H_out, Stride::contiguous_along_axis(3, H_out)); H_out.datatype() = DataType::Float32; H_out.datatype().set_byte_order_native(); - auto out = Image::create (argument[2], H_out); - + auto out = Image::create(argument[2], H_out); if (colourmap.is_colour) { - assert (fixed_colour.allFinite()); - for (auto l_outer = Loop ("Applying fixed RGB colour to greyscale image", H_in) (in, out); l_outer; ++l_outer) { - const float amplitude = std::max (0.0f, std::min (1.0f, scale (in.value()))); - for (auto l_inner = Loop(3) (out); l_inner; ++l_inner) + assert(fixed_colour.allFinite()); + for (auto l_outer = Loop("Applying fixed RGB colour to greyscale image", H_in)(in, out); l_outer; ++l_outer) { + const float amplitude = std::max(0.0f, std::min(1.0f, scale(in.value()))); + for (auto l_inner = Loop(3)(out); l_inner; ++l_inner) out.value() = amplitude * fixed_colour[out.index(3)]; } } else if (colourmap.is_rgb) { - for (auto l_outer = Loop ("Scaling RGB colour image", H_in) (in, out); l_outer; ++l_outer) - out.value() = scale (in.value()); + for (auto l_outer = Loop("Scaling RGB colour image", H_in)(in, out); l_outer; ++l_outer) + out.value() = scale(in.value()); } else { - const ColourMap::Entry::basic_map_fn& map_fn = colourmap.basic_mapping; - for (auto l_outer = Loop ("Mapping intensities to RGB colours", H_in) (in, out); l_outer; ++l_outer) { - const Eigen::Array3f colour = map_fn (scale (in.value())); - for (auto l_inner = Loop(3) (out); l_inner; ++l_inner) + const ColourMap::Entry::basic_map_fn &map_fn = colourmap.basic_mapping; + for (auto l_outer = Loop("Mapping intensities to RGB colours", H_in)(in, out); l_outer; ++l_outer) { + const Eigen::Array3f colour = map_fn(scale(in.value())); + for (auto l_inner = Loop(3)(out); l_inner; ++l_inner) out.value() = colour[out.index(3)]; } } diff --git a/cmd/mrconvert.cpp b/cmd/mrconvert.cpp index 71df4dd43e..50f20236f5 100644 --- a/cmd/mrconvert.cpp +++ b/cmd/mrconvert.cpp @@ -14,251 +14,225 @@ * For more details, see http://www.mrtrix.org/. */ +#include "adapter/extract.h" +#include "adapter/permute_axes.h" +#include "algo/threaded_copy.h" #include "axes.h" #include "command.h" +#include "dwi/gradient.h" +#include "file/json_utils.h" +#include "file/ofstream.h" #include "header.h" #include "image.h" #include "phase_encoding.h" #include "transform.h" #include "types.h" -#include "algo/threaded_copy.h" -#include "adapter/extract.h" -#include "adapter/permute_axes.h" -#include "file/json_utils.h" -#include "file/ofstream.h" -#include "dwi/gradient.h" - using namespace MR; using namespace App; - - bool add_to_command_history = true; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com) and Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Perform conversion between different file types and optionally " "extract a subset of the input image"; DESCRIPTION - + "If used correctly, this program can be a very useful workhorse. " - "In addition to converting images between different formats, it can " - "be used to extract specific studies from a data set, extract a " - "specific region of interest, or flip the images. Some of the possible " - "operations are described in more detail below." - - + "Note that for both the -coord and -axes options, indexing starts from 0 " - "rather than 1. E.g. " - "-coord 3 <#> selects volumes (the fourth dimension) from the series; " - "-axes 0,1,2 includes only the three spatial axes in the output image." - - + "Additionally, for the second input to the -coord option and the -axes " - "option, you can use any valid number sequence in the selection, as well " - "as the 'end' keyword (see the main documentation for details); this can be " - "particularly useful to select multiple coordinates." - - + "The -vox option is used to change the size of the voxels in the output " - "image as reported in the image header; note however that this does not " - "re-sample the image based on a new voxel size (that is done using the " - "mrgrid command)." - - + "By default, the intensity scaling parameters in the input image header " - "are passed through to the output image header when writing to an integer " - "image, and reset to 0,1 (i.e. no scaling) for floating-point and binary " - "images. Note that the -scaling option will therefore have no effect for " - "floating-point or binary output images." - - + "The -axes option specifies which axes from the input image will be used " - "to form the output image. This allows the permutation, omission, or " - "addition of axes into the output image. The axes should be supplied as a " - "comma-separated list of axis indices. If an axis from the input image is " - "to be omitted from the output image, it must either already have a size of " - "1, or a single coordinate along that axis must be selected by the user by " - "using the -coord option. Examples are provided further below." - - + DWI::bvalue_scaling_description; - + +"If used correctly, this program can be a very useful workhorse. " + "In addition to converting images between different formats, it can " + "be used to extract specific studies from a data set, extract a " + "specific region of interest, or flip the images. Some of the possible " + "operations are described in more detail below." + + + "Note that for both the -coord and -axes options, indexing starts from 0 " + "rather than 1. E.g. " + "-coord 3 <#> selects volumes (the fourth dimension) from the series; " + "-axes 0,1,2 includes only the three spatial axes in the output image." + + + "Additionally, for the second input to the -coord option and the -axes " + "option, you can use any valid number sequence in the selection, as well " + "as the 'end' keyword (see the main documentation for details); this can be " + "particularly useful to select multiple coordinates." + + + "The -vox option is used to change the size of the voxels in the output " + "image as reported in the image header; note however that this does not " + "re-sample the image based on a new voxel size (that is done using the " + "mrgrid command)." + + + "By default, the intensity scaling parameters in the input image header " + "are passed through to the output image header when writing to an integer " + "image, and reset to 0,1 (i.e. no scaling) for floating-point and binary " + "images. Note that the -scaling option will therefore have no effect for " + "floating-point or binary output images." + + + "The -axes option specifies which axes from the input image will be used " + "to form the output image. This allows the permutation, omission, or " + "addition of axes into the output image. The axes should be supplied as a " + "comma-separated list of axis indices. If an axis from the input image is " + "to be omitted from the output image, it must either already have a size of " + "1, or a single coordinate along that axis must be selected by the user by " + "using the -coord option. Examples are provided further below." + + + DWI::bvalue_scaling_description; EXAMPLES - + Example ("Extract the first volume from a 4D image, and make the output a 3D image", - "mrconvert in.mif -coord 3 0 -axes 0,1,2 out.mif", - "The -coord 3 0 option extracts, from axis number 3 (which is the " - "fourth axis since counting begins from 0; this is the axis that " - "steps across image volumes), only coordinate number 0 (i.e. the " - "first volume). The -axes 0,1,2 ensures that only the first three " - "axes (i.e. the spatial axes) are retained; if this option were not " - "used in this example, then image out.mif would be a 4D image, " - "but it would only consist of a single volume, and mrinfo would " - "report its size along the fourth axis as 1.") - - + Example ("Extract slice number 24 along the AP direction", - "mrconvert volume.mif slice.mif -coord 1 24", - "MRtrix3 uses a RAS (Right-Anterior-Superior) axis " - "convention, and internally reorients images upon loading " - "in order to conform to this as far as possible. So for " - "non-exotic data, axis 1 should correspond (approximately) to the " - "anterior-posterior direction.") - - + Example ("Extract only every other volume from a 4D image", - "mrconvert all.mif every_other.mif -coord 3 1:2:end", - "This example demonstrates two features: Use of the " - "colon syntax to conveniently specify a number sequence " - "(in the format \'start:step:stop\'); and use of the \'end\' " - "keyword to generate this sequence up to the size of the " - "input image along that axis (i.e. the number of volumes).") - - + Example ("Alter the image header to report a new isotropic voxel size", - "mrconvert in.mif isotropic.mif -vox 1.25", - "By providing a single value to the -vox option only, the " - "specified value is used to set the voxel size in mm for all " - "three spatial axes in the output image.") - - + Example ("Alter the image header to report a new anisotropic voxel size", - "mrconvert in.mif anisotropic.mif -vox 1,,3.5", - "This example will change the reported voxel size along the first " - "and third axes (ideally left-right and inferior-superior) to " - "1.0mm and 3.5mm respectively, and leave the voxel size along the " - "second axis (ideally anterior-posterior) unchanged.") - - + Example ("Turn a single-volume 4D image into a 3D image", - "mrconvert 4D.mif 3D.mif -axes 0,1,2", - "Sometimes in the process of extracting or calculating a single " - "3D volume from a 4D image series, the size of the image reported " - "by mrinfo will be \"X x Y x Z x 1\", indicating that the resulting " - "image is in fact also 4D, it just happens to contain only one " - "volume. This example demonstrates how to convert this into a " - "genuine 3D image (i.e. mrinfo will report the size as \"X x Y x Z\".") - - + Example ("Insert an axis of size 1 into the image", - "mrconvert XYZD.mif XYZ1D.mif -axes 0,1,2,-1,3", - "This example uses the value -1 as a flag to indicate to mrconvert " - "where a new axis of unity size is to be inserted. In this particular " - "example, the input image has four axes: the spatial axes X, Y and Z, " - "and some form of data D is stored across the fourth axis (i.e. " - "volumes). Due to insertion of a new axis, the output image is 5D: " - "the three spatial axes (XYZ), a single volume (the size of the " - "output image along the fourth axis will be 1), and data D " - "will be stored as volume groups along the fifth axis of the image.") - - + Example ("Manually reset the data scaling parameters stored within the image header to defaults", - "mrconvert with_scaling.mif without_scaling.mif -scaling 0.0,1.0", - "This command-line option alters the parameters stored within the image " - "header that provide a linear mapping from raw intensity values stored " - "in the image data to some other scale. Where the raw data stored in a " - "particular voxel is I, the value within that voxel is interpreted as: " - "value = offset + (scale x I). To adjust this scaling, the relevant " - "parameters must be provided as a comma-separated 2-vector of " - "floating-point values, in the format \"offset,scale\" (no quotation " - "marks). This particular example sets the offset to zero and the scale " - "to one, which equates to no rescaling of the raw intensity data."); - + +Example("Extract the first volume from a 4D image, and make the output a 3D image", + "mrconvert in.mif -coord 3 0 -axes 0,1,2 out.mif", + "The -coord 3 0 option extracts, from axis number 3 (which is the " + "fourth axis since counting begins from 0; this is the axis that " + "steps across image volumes), only coordinate number 0 (i.e. the " + "first volume). The -axes 0,1,2 ensures that only the first three " + "axes (i.e. the spatial axes) are retained; if this option were not " + "used in this example, then image out.mif would be a 4D image, " + "but it would only consist of a single volume, and mrinfo would " + "report its size along the fourth axis as 1.") + + + Example("Extract slice number 24 along the AP direction", + "mrconvert volume.mif slice.mif -coord 1 24", + "MRtrix3 uses a RAS (Right-Anterior-Superior) axis " + "convention, and internally reorients images upon loading " + "in order to conform to this as far as possible. So for " + "non-exotic data, axis 1 should correspond (approximately) to the " + "anterior-posterior direction.") + + + Example("Extract only every other volume from a 4D image", + "mrconvert all.mif every_other.mif -coord 3 1:2:end", + "This example demonstrates two features: Use of the " + "colon syntax to conveniently specify a number sequence " + "(in the format \'start:step:stop\'); and use of the \'end\' " + "keyword to generate this sequence up to the size of the " + "input image along that axis (i.e. the number of volumes).") + + + Example("Alter the image header to report a new isotropic voxel size", + "mrconvert in.mif isotropic.mif -vox 1.25", + "By providing a single value to the -vox option only, the " + "specified value is used to set the voxel size in mm for all " + "three spatial axes in the output image.") + + + Example("Alter the image header to report a new anisotropic voxel size", + "mrconvert in.mif anisotropic.mif -vox 1,,3.5", + "This example will change the reported voxel size along the first " + "and third axes (ideally left-right and inferior-superior) to " + "1.0mm and 3.5mm respectively, and leave the voxel size along the " + "second axis (ideally anterior-posterior) unchanged.") + + + Example("Turn a single-volume 4D image into a 3D image", + "mrconvert 4D.mif 3D.mif -axes 0,1,2", + "Sometimes in the process of extracting or calculating a single " + "3D volume from a 4D image series, the size of the image reported " + "by mrinfo will be \"X x Y x Z x 1\", indicating that the resulting " + "image is in fact also 4D, it just happens to contain only one " + "volume. This example demonstrates how to convert this into a " + "genuine 3D image (i.e. mrinfo will report the size as \"X x Y x Z\".") + + + Example("Insert an axis of size 1 into the image", + "mrconvert XYZD.mif XYZ1D.mif -axes 0,1,2,-1,3", + "This example uses the value -1 as a flag to indicate to mrconvert " + "where a new axis of unity size is to be inserted. In this particular " + "example, the input image has four axes: the spatial axes X, Y and Z, " + "and some form of data D is stored across the fourth axis (i.e. " + "volumes). Due to insertion of a new axis, the output image is 5D: " + "the three spatial axes (XYZ), a single volume (the size of the " + "output image along the fourth axis will be 1), and data D " + "will be stored as volume groups along the fifth axis of the image.") + + + Example("Manually reset the data scaling parameters stored within the image header to defaults", + "mrconvert with_scaling.mif without_scaling.mif -scaling 0.0,1.0", + "This command-line option alters the parameters stored within the image " + "header that provide a linear mapping from raw intensity values stored " + "in the image data to some other scale. Where the raw data stored in a " + "particular voxel is I, the value within that voxel is interpreted as: " + "value = offset + (scale x I). To adjust this scaling, the relevant " + "parameters must be provided as a comma-separated 2-vector of " + "floating-point values, in the format \"offset,scale\" (no quotation " + "marks). This particular example sets the offset to zero and the scale " + "to one, which equates to no rescaling of the raw intensity data."); ARGUMENTS - + Argument ("input", "the input image.").type_image_in () - + Argument ("output", "the output image.").type_image_out (); + +Argument("input", "the input image.").type_image_in() + Argument("output", "the output image.").type_image_out(); OPTIONS - + OptionGroup ("Options for manipulating fundamental image properties") + +OptionGroup("Options for manipulating fundamental image properties") - + Option ("coord", - "retain data from the input image only at the coordinates " - "specified in the selection along the specified axis. The selection " - "argument expects a number sequence, which can also include the " - "'end' keyword.").allow_multiple() - + Argument ("axis").type_integer (0) - + Argument ("selection").type_sequence_int() + + Option("coord", + "retain data from the input image only at the coordinates " + "specified in the selection along the specified axis. The selection " + "argument expects a number sequence, which can also include the " + "'end' keyword.") + .allow_multiple() + + Argument("axis").type_integer(0) + Argument("selection").type_sequence_int() - + Option ("vox", - "change the voxel dimensions reported in the output image header") - + Argument ("sizes").type_sequence_float() + + Option("vox", "change the voxel dimensions reported in the output image header") + + Argument("sizes").type_sequence_float() - + Option ("axes", - "specify the axes from the input image that will be used to form the output image") - + Argument ("axes").type_sequence_int() + + Option("axes", "specify the axes from the input image that will be used to form the output image") + + Argument("axes").type_sequence_int() - + Option ("scaling", - "specify the data scaling parameters used to rescale the intensity values") - + Argument ("values").type_sequence_float() + + Option("scaling", "specify the data scaling parameters used to rescale the intensity values") + + Argument("values").type_sequence_float() + + OptionGroup("Options for handling JSON (JavaScript Object Notation) files") - + OptionGroup ("Options for handling JSON (JavaScript Object Notation) files") + + Option("json_import", "import data from a JSON file into header key-value pairs") + + Argument("file").type_file_in() - + Option ("json_import", "import data from a JSON file into header key-value pairs") - + Argument ("file").type_file_in() + + Option("json_export", "export data from an image header key-value pairs into a JSON file") + + Argument("file").type_file_out() - + Option ("json_export", "export data from an image header key-value pairs into a JSON file") - + Argument ("file").type_file_out() + + OptionGroup("Options to modify generic header entries") + + Option("clear_property", "remove the specified key from the image header altogether.").allow_multiple() + + Argument("key").type_text() - + OptionGroup ("Options to modify generic header entries") + + Option("set_property", "set the value of the specified key in the image header.").allow_multiple() + + Argument("key").type_text() + Argument("value").type_text() - + Option ("clear_property", - "remove the specified key from the image header altogether.").allow_multiple() - + Argument ("key").type_text() + + Option("append_property", + "append the given value to the specified key in the image header (this adds the value specified as a " + "new line in the header value).") + .allow_multiple() + + Argument("key").type_text() + Argument("value").type_text() - + Option ("set_property", - "set the value of the specified key in the image header.").allow_multiple() - + Argument ("key").type_text() - + Argument ("value").type_text() + + Option("copy_properties", + "clear all generic properties and replace with the properties from the image / file specified.") + + Argument("source").type_various() - + Option ("append_property", - "append the given value to the specified key in the image header (this adds the value specified as a new line in the header value).").allow_multiple() - + Argument ("key").type_text() - + Argument ("value").type_text() + + Stride::Options - + Option ("copy_properties", - "clear all generic properties and replace with the properties from the image / file specified.") - + Argument ("source").type_various() + + DataType::options() + + DWI::GradImportOptions() + DWI::bvalue_scaling_option - + Stride::Options + + DWI::GradExportOptions() - + DataType::options() - - + DWI::GradImportOptions () - + DWI::bvalue_scaling_option - - + DWI::GradExportOptions() - - + PhaseEncoding::ImportOptions - + PhaseEncoding::ExportOptions; + + PhaseEncoding::ImportOptions + PhaseEncoding::ExportOptions; } - - - - - - -void permute_DW_scheme (Header& H, const vector& axes) -{ - auto in = DWI::parse_DW_scheme (H); +void permute_DW_scheme(Header &H, const vector &axes) { + auto in = DWI::parse_DW_scheme(H); if (!in.rows()) return; - Transform T (H); + Transform T(H); Eigen::Matrix3d permute = Eigen::Matrix3d::Zero(); for (size_t axis = 0; axis != 3; ++axis) permute(axes[axis], axis) = 1.0; const Eigen::Matrix3d R = T.scanner2voxel.rotation() * permute * T.voxel2scanner.rotation(); - Eigen::MatrixXd out (in.rows(), in.cols()); - out.block(0, 3, out.rows(), out.cols()-3) = in.block(0, 3, in.rows(), in.cols()-3); // Copy b-values (and anything else stored in dw_scheme) + Eigen::MatrixXd out(in.rows(), in.cols()); + out.block(0, 3, out.rows(), out.cols() - 3) = + in.block(0, 3, in.rows(), in.cols() - 3); // Copy b-values (and anything else stored in dw_scheme) for (int row = 0; row != in.rows(); ++row) - out.block<1,3>(row, 0) = in.block<1,3>(row, 0) * R; + out.block<1, 3>(row, 0) = in.block<1, 3>(row, 0) * R; - DWI::set_DW_scheme (H, out); + DWI::set_DW_scheme(H, out); } - - -void permute_PE_scheme (Header& H, const vector& axes) -{ - auto in = PhaseEncoding::parse_scheme (H); +void permute_PE_scheme(Header &H, const vector &axes) { + auto in = PhaseEncoding::parse_scheme(H); if (!in.rows()) return; @@ -266,32 +240,25 @@ void permute_PE_scheme (Header& H, const vector& axes) for (size_t axis = 0; axis != 3; ++axis) permute(axes[axis], axis) = 1.0; - Eigen::MatrixXd out (in.rows(), in.cols()); - out.block(0, 3, out.rows(), out.cols()-3) = in.block(0, 3, in.rows(), in.cols()-3); // Copy total readout times (and anything else stored in pe_scheme) + Eigen::MatrixXd out(in.rows(), in.cols()); + out.block(0, 3, out.rows(), out.cols() - 3) = + in.block(0, 3, in.rows(), in.cols() - 3); // Copy total readout times (and anything else stored in pe_scheme) for (int row = 0; row != in.rows(); ++row) - out.block<1,3>(row, 0) = in.block<1,3>(row, 0) * permute; + out.block<1, 3>(row, 0) = in.block<1, 3>(row, 0) * permute; - PhaseEncoding::set_scheme (H, out); + PhaseEncoding::set_scheme(H, out); } - - -void permute_slice_direction (Header& H, const vector& axes) -{ - auto it = H.keyval().find ("SliceEncodingDirection"); +void permute_slice_direction(Header &H, const vector &axes) { + auto it = H.keyval().find("SliceEncodingDirection"); if (it == H.keyval().end()) return; - const Eigen::Vector3d orig_dir = Axes::id2dir (it->second); - const Eigen::Vector3d new_dir (orig_dir[axes[0]], orig_dir[axes[1]], orig_dir[axes[2]]); - it->second = Axes::dir2id (new_dir); + const Eigen::Vector3d orig_dir = Axes::id2dir(it->second); + const Eigen::Vector3d new_dir(orig_dir[axes[0]], orig_dir[axes[1]], orig_dir[axes[2]]); + it->second = Axes::dir2id(new_dir); } - - - -template -inline vector set_header (Header& header, const ImageType& input) -{ +template inline vector set_header(Header &header, const ImageType &input) { header.ndim() = input.ndim(); for (size_t n = 0; n < header.ndim(); ++n) { header.size(n) = input.size(n); @@ -300,269 +267,244 @@ inline vector set_header (Header& header, const ImageType& input) } header.transform() = input.transform(); - auto opt = get_options ("axes"); + auto opt = get_options("axes"); vector axes; if (opt.size()) { - axes = parse_ints (opt[0][0]); + axes = parse_ints(opt[0][0]); header.ndim() = axes.size(); for (size_t i = 0; i < axes.size(); ++i) { - if (axes[i] >= static_cast (input.ndim())) - throw Exception ("axis supplied to option -axes is out of bounds"); - header.size(i) = axes[i] < 0 ? 1 : input.size (axes[i]); - header.spacing(i) = axes[i] < 0 ? NaN : input.spacing (axes[i]); + if (axes[i] >= static_cast(input.ndim())) + throw Exception("axis supplied to option -axes is out of bounds"); + header.size(i) = axes[i] < 0 ? 1 : input.size(axes[i]); + header.spacing(i) = axes[i] < 0 ? NaN : input.spacing(axes[i]); } - permute_DW_scheme (header, axes); - permute_PE_scheme (header, axes); - permute_slice_direction (header, axes); + permute_DW_scheme(header, axes); + permute_PE_scheme(header, axes); + permute_slice_direction(header, axes); } else { header.ndim() = input.ndim(); - axes.assign (input.ndim(), 0); + axes.assign(input.ndim(), 0); for (size_t i = 0; i < axes.size(); ++i) { axes[i] = i; - header.size (i) = input.size (i); + header.size(i) = input.size(i); } } - opt = get_options ("vox"); + opt = get_options("vox"); if (opt.size()) { - vector vox = parse_floats (opt[0][0]); + vector vox = parse_floats(opt[0][0]); if (vox.size() > header.ndim()) - throw Exception ("too many axes supplied to -vox option"); + throw Exception("too many axes supplied to -vox option"); if (vox.size() == 1) - vox.resize (3, vox[0]); + vox.resize(3, vox[0]); for (size_t n = 0; n < vox.size(); ++n) { - if (std::isfinite (vox[n])) + if (std::isfinite(vox[n])) header.spacing(n) = vox[n]; } } - Stride::set_from_command_line (header); + Stride::set_from_command_line(header); return axes; } - - - - - template -void copy_permute (const InputType& in, Header& header_out, const std::string& output_filename) -{ - const auto axes = set_header (header_out, in); - auto out = Image::create (output_filename, header_out, add_to_command_history); - DWI::export_grad_commandline (out); - PhaseEncoding::export_commandline (out); - auto perm = Adapter::make (in, axes); - threaded_copy_with_progress (perm, out, 0, std::numeric_limits::max(), 2); +void copy_permute(const InputType &in, Header &header_out, const std::string &output_filename) { + const auto axes = set_header(header_out, in); + auto out = Image::create(output_filename, header_out, add_to_command_history); + DWI::export_grad_commandline(out); + PhaseEncoding::export_commandline(out); + auto perm = Adapter::make(in, axes); + threaded_copy_with_progress(perm, out, 0, std::numeric_limits::max(), 2); } - - - - - template -void extract (Header& header_in, Header& header_out, const vector>& pos, const std::string& output_filename) -{ +void extract(Header &header_in, + Header &header_out, + const vector> &pos, + const std::string &output_filename) { auto in = header_in.get_image(); if (pos.empty()) { - copy_permute (in, header_out, output_filename); + copy_permute(in, header_out, output_filename); } else { - auto extract = Adapter::make (in, pos); - copy_permute (extract, header_out, output_filename); + auto extract = Adapter::make(in, pos); + copy_permute(extract, header_out, output_filename); } } - - - - - - - - - -void run () -{ - Header header_in = Header::open (argument[0]); +void run() { + Header header_in = Header::open(argument[0]); Eigen::MatrixXd dw_scheme; try { - dw_scheme = DWI::get_DW_scheme (header_in, DWI::get_cmdline_bvalue_scaling_behaviour()); - } - catch (Exception& e) { - if (get_options ("grad").size() || get_options ("fslgrad").size() || get_options ("bvalue_scaling").size()) + dw_scheme = DWI::get_DW_scheme(header_in, DWI::get_cmdline_bvalue_scaling_behaviour()); + } catch (Exception &e) { + if (get_options("grad").size() || get_options("fslgrad").size() || get_options("bvalue_scaling").size()) throw; - e.display (2); + e.display(2); } - Header header_out (header_in); - header_out.datatype() = DataType::from_command_line (header_out.datatype()); + Header header_out(header_in); + header_out.datatype() = DataType::from_command_line(header_out.datatype()); if (header_in.datatype().is_complex() && !header_out.datatype().is_complex()) - WARN ("requested datatype is real but input datatype is complex - imaginary component will be ignored"); + WARN("requested datatype is real but input datatype is complex - imaginary component will be ignored"); - if (get_options ("import_pe_table").size() || get_options ("import_pe_eddy").size()) - PhaseEncoding::set_scheme (header_out, PhaseEncoding::get_scheme (header_in)); + if (get_options("import_pe_table").size() || get_options("import_pe_eddy").size()) + PhaseEncoding::set_scheme(header_out, PhaseEncoding::get_scheme(header_in)); - auto opt = get_options ("json_import"); + auto opt = get_options("json_import"); if (opt.size()) - File::JSON::load (header_out, opt[0][0]); - - + File::JSON::load(header_out, opt[0][0]); - opt = get_options ("copy_properties"); + opt = get_options("copy_properties"); if (opt.size()) { header_out.keyval().clear(); if (str(opt[0][0]) != "NULL") { try { - const Header source = Header::open (opt[0][0]); + const Header source = Header::open(opt[0][0]); header_out.keyval() = source.keyval(); } catch (...) { try { - File::JSON::load (header_out, opt[0][0]); + File::JSON::load(header_out, opt[0][0]); } catch (...) { - throw Exception ("Unable to obtain header key-value entries from spec \"" + str(opt[0][0]) + "\""); + throw Exception("Unable to obtain header key-value entries from spec \"" + str(opt[0][0]) + "\""); } } } } - opt = get_options ("clear_property"); + opt = get_options("clear_property"); for (size_t n = 0; n < opt.size(); ++n) { if (str(opt[n][0]) == "command_history") add_to_command_history = false; - auto entry = header_out.keyval().find (opt[n][0]); + auto entry = header_out.keyval().find(opt[n][0]); if (entry == header_out.keyval().end()) { if (std::string(opt[n][0]) != "command_history") { - WARN ("No header key/value entry \"" + opt[n][0] + "\" found; ignored"); + WARN("No header key/value entry \"" + opt[n][0] + "\" found; ignored"); } } else { - header_out.keyval().erase (entry); + header_out.keyval().erase(entry); } } - opt = get_options ("set_property"); + opt = get_options("set_property"); for (size_t n = 0; n < opt.size(); ++n) { if (str(opt[n][0]) == "command_history") add_to_command_history = false; header_out.keyval()[opt[n][0].as_text()] = opt[n][1].as_text(); } - opt = get_options ("append_property"); + opt = get_options("append_property"); for (size_t n = 0; n < opt.size(); ++n) { if (str(opt[n][0]) == "command_history") add_to_command_history = false; - add_line (header_out.keyval()[opt[n][0].as_text()], opt[n][1].as_text()); + add_line(header_out.keyval()[opt[n][0].as_text()], opt[n][1].as_text()); } - - - - opt = get_options ("coord"); + opt = get_options("coord"); vector> pos; if (opt.size()) { - pos.assign (header_in.ndim(), vector()); + pos.assign(header_in.ndim(), vector()); for (size_t n = 0; n < opt.size(); n++) { size_t axis = opt[n][0]; if (axis >= header_in.ndim()) - throw Exception ("axis " + str(axis) + " provided with -coord option is out of range of input image"); + throw Exception("axis " + str(axis) + " provided with -coord option is out of range of input image"); if (pos[axis].size()) - throw Exception ("\"coord\" option specified twice for axis " + str (axis)); - pos[axis] = parse_ints (opt[n][1], header_in.size(axis)-1); + throw Exception("\"coord\" option specified twice for axis " + str(axis)); + pos[axis] = parse_ints(opt[n][1], header_in.size(axis) - 1); auto minval = std::min_element(std::begin(pos[axis]), std::end(pos[axis])); if (*minval < 0) - throw Exception ("coordinate position " + str(*minval) + " for axis " + str(axis) + " provided with -coord option is negative"); + throw Exception("coordinate position " + str(*minval) + " for axis " + str(axis) + + " provided with -coord option is negative"); auto maxval = std::max_element(std::begin(pos[axis]), std::end(pos[axis])); if (*maxval >= header_in.size(axis)) - throw Exception ("coordinate position " + str(*maxval) + " for axis " + str(axis) + " provided with -coord option is out of range of input image"); + throw Exception("coordinate position " + str(*maxval) + " for axis " + str(axis) + + " provided with -coord option is out of range of input image"); - header_out.size (axis) = pos[axis].size(); + header_out.size(axis) = pos[axis].size(); if (axis == 3) { - const auto grad = DWI::parse_DW_scheme (header_out); + const auto grad = DWI::parse_DW_scheme(header_out); if (grad.rows()) { if ((ssize_t)grad.rows() != header_in.size(3)) { - WARN ("Diffusion encoding of input file does not match number of image volumes; omitting gradient information from output image"); - DWI::clear_DW_scheme (header_out); + WARN("Diffusion encoding of input file does not match number of image volumes; omitting gradient " + "information from output image"); + DWI::clear_DW_scheme(header_out); } else { - Eigen::MatrixXd extract_grad (pos[3].size(), grad.cols()); + Eigen::MatrixXd extract_grad(pos[3].size(), grad.cols()); for (size_t dir = 0; dir != pos[3].size(); ++dir) - extract_grad.row (dir) = grad.row (pos[3][dir]); - DWI::set_DW_scheme (header_out, extract_grad); + extract_grad.row(dir) = grad.row(pos[3][dir]); + DWI::set_DW_scheme(header_out, extract_grad); } } Eigen::MatrixXd pe_scheme; try { - pe_scheme = PhaseEncoding::get_scheme (header_in); + pe_scheme = PhaseEncoding::get_scheme(header_in); if (pe_scheme.rows()) { - Eigen::MatrixXd extract_scheme (pos[3].size(), pe_scheme.cols()); + Eigen::MatrixXd extract_scheme(pos[3].size(), pe_scheme.cols()); for (size_t vol = 0; vol != pos[3].size(); ++vol) - extract_scheme.row (vol) = pe_scheme.row (pos[3][vol]); - PhaseEncoding::set_scheme (header_out, extract_scheme); + extract_scheme.row(vol) = pe_scheme.row(pos[3][vol]); + PhaseEncoding::set_scheme(header_out, extract_scheme); } } catch (...) { - WARN ("Phase encoding scheme of input file does not match number of image volumes; omitting information from output image"); - PhaseEncoding::set_scheme (header_out, Eigen::MatrixXd()); + WARN("Phase encoding scheme of input file does not match number of image volumes; omitting information from " + "output image"); + PhaseEncoding::set_scheme(header_out, Eigen::MatrixXd()); } } } for (size_t n = 0; n < header_in.ndim(); ++n) { if (pos[n].empty()) { - pos[n].resize (header_in.size (n)); + pos[n].resize(header_in.size(n)); for (uint32_t i = 0; i < uint32_t(pos[n].size()); i++) pos[n][i] = i; } } } - - opt = get_options ("scaling"); + opt = get_options("scaling"); if (opt.size()) { if (header_out.datatype().is_integer()) { vector scaling = opt[0][0]; if (scaling.size() != 2) - throw Exception ("-scaling option expects comma-separated 2-vector of floating-point values"); + throw Exception("-scaling option expects comma-separated 2-vector of floating-point values"); header_out.intensity_offset() = scaling[0]; - header_out.intensity_scale() = scaling[1]; - } - else - WARN ("-scaling option has no effect for floating-point or binary images"); + header_out.intensity_scale() = scaling[1]; + } else + WARN("-scaling option has no effect for floating-point or binary images"); } - - if (header_out.intensity_offset() == 0.0 && header_out.intensity_scale() == 1.0 && !header_out.datatype().is_floating_point()) { + if (header_out.intensity_offset() == 0.0 && header_out.intensity_scale() == 1.0 && + !header_out.datatype().is_floating_point()) { switch (header_out.datatype()() & DataType::Type) { - case DataType::Bit: - case DataType::UInt8: - case DataType::UInt16: - case DataType::UInt32: - if (header_out.datatype().is_signed()) - extract (header_in, header_out, pos, argument[1]); - else - extract (header_in, header_out, pos, argument[1]); - break; - case DataType::UInt64: - if (header_out.datatype().is_signed()) - extract (header_in, header_out, pos, argument[1]); - else - extract (header_in, header_out, pos, argument[1]); - break; - case DataType::Undefined: throw Exception ("invalid output image data type"); break; - + case DataType::Bit: + case DataType::UInt8: + case DataType::UInt16: + case DataType::UInt32: + if (header_out.datatype().is_signed()) + extract(header_in, header_out, pos, argument[1]); + else + extract(header_in, header_out, pos, argument[1]); + break; + case DataType::UInt64: + if (header_out.datatype().is_signed()) + extract(header_in, header_out, pos, argument[1]); + else + extract(header_in, header_out, pos, argument[1]); + break; + case DataType::Undefined: + throw Exception("invalid output image data type"); + break; } - } - else { + } else { if (header_out.datatype().is_complex()) - extract (header_in, header_out, pos, argument[1]); + extract(header_in, header_out, pos, argument[1]); else - extract (header_in, header_out, pos, argument[1]); + extract(header_in, header_out, pos, argument[1]); } - - opt = get_options ("json_export"); + opt = get_options("json_export"); if (opt.size()) - File::JSON::save (header_out, opt[0][0], argument[1]); + File::JSON::save(header_out, opt[0][0], argument[1]); } - diff --git a/cmd/mrdegibbs.cpp b/cmd/mrdegibbs.cpp index 9a5cb09b80..c528d30e0a 100644 --- a/cmd/mrdegibbs.cpp +++ b/cmd/mrdegibbs.cpp @@ -23,173 +23,162 @@ using namespace MR; using namespace App; +const char *modes[] = {"2d", "3d", nullptr}; - -const char* modes[] = { "2d", "3d", nullptr }; - -void usage () -{ +void usage() { AUTHOR = "Ben Jeurissen (ben.jeurissen@uantwerpen.be) & J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Remove Gibbs Ringing Artifacts"; DESCRIPTION - + "This application attempts to remove Gibbs ringing artefacts from MRI images using the method " - "of local subvoxel-shifts proposed by Kellner et al. (see reference below for details). By default, " - "the original 2D slice-wise version is used. If the -mode 3d option is provided, the program will run " - "the 3D version as proposed by Bautista et al. (also in the reference list below)." - - + "This command is designed to run on data directly after it has been reconstructed by the scanner, " - "before any interpolation of any kind has taken place. You should not run this command after any " - "form of motion correction (e.g. not after dwifslpreproc). Similarly, if you intend running dwidenoise, " - "you should run denoising before this command to not alter the noise structure, " - "which would impact on dwidenoise's performance." - - + "Note that this method is designed to work on images acquired with full k-space coverage. " - "Running this method on partial Fourier ('half-scan') or filtered data may not remove all ringing " - "artefacts. Users are encouraged to acquired full-Fourier data where possible, and disable any " - "form of filtering on the scanner."; - + +"This application attempts to remove Gibbs ringing artefacts from MRI images using the method " + "of local subvoxel-shifts proposed by Kellner et al. (see reference below for details). By default, " + "the original 2D slice-wise version is used. If the -mode 3d option is provided, the program will run " + "the 3D version as proposed by Bautista et al. (also in the reference list below)." + + + "This command is designed to run on data directly after it has been reconstructed by the scanner, " + "before any interpolation of any kind has taken place. You should not run this command after any " + "form of motion correction (e.g. not after dwifslpreproc). Similarly, if you intend running dwidenoise, " + "you should run denoising before this command to not alter the noise structure, " + "which would impact on dwidenoise's performance." + + + "Note that this method is designed to work on images acquired with full k-space coverage. " + "Running this method on partial Fourier ('half-scan') or filtered data may not remove all ringing " + "artefacts. Users are encouraged to acquired full-Fourier data where possible, and disable any " + "form of filtering on the scanner."; ARGUMENTS - + Argument ("in", "the input image.").type_image_in () - + Argument ("out", "the output image.").type_image_out (); - + +Argument("in", "the input image.").type_image_in() + Argument("out", "the output image.").type_image_out(); OPTIONS - + Option ("mode", - "specify the mode of operation. Valid choices are: 2d, 3d (default: " - "2d). The 2d mode corresponds to the original slice-wise approach as " - "propoosed by Kellner et al., appropriate for images acquired using " - "2D muli-slice approaches. The 3d mode corresponds to the 3D " - "volume-wise extension proposed by Bautista et al., which is " - "appropriate for images acquired using 3D Fourier encoding.") - + Argument ("type").type_choice (modes) - - + Option ("axes", - "select the slice axes (default: 0,1 - i.e. x-y). Select all 3 spatial axes for 3D operation, " - "i.e. 0:2 or 0,1,2 (this is equivalent to '-mode 3d').") - + Argument ("list").type_sequence_int () + +Option("mode", + "specify the mode of operation. Valid choices are: 2d, 3d (default: " + "2d). The 2d mode corresponds to the original slice-wise approach as " + "propoosed by Kellner et al., appropriate for images acquired using " + "2D muli-slice approaches. The 3d mode corresponds to the 3D " + "volume-wise extension proposed by Bautista et al., which is " + "appropriate for images acquired using 3D Fourier encoding.") + + Argument("type").type_choice(modes) - + Option ("nshifts", "discretization of subpixel spacing (default: 20).") - + Argument ("value").type_integer (8, 128) + + Option("axes", + "select the slice axes (default: 0,1 - i.e. x-y). Select all 3 spatial axes for 3D operation, " + "i.e. 0:2 or 0,1,2 (this is equivalent to '-mode 3d').") + + Argument("list").type_sequence_int() - + Option ("minW", "left border of window used for TV computation (default: 1).") - + Argument ("value").type_integer (0, 10) + + Option("nshifts", "discretization of subpixel spacing (default: 20).") + Argument("value").type_integer(8, 128) - + Option ("maxW", "right border of window used for TV computation (default: 3).") - + Argument ("value").type_integer (0, 128) + + Option("minW", "left border of window used for TV computation (default: 1).") + + Argument("value").type_integer(0, 10) - + DataType::options(); + + Option("maxW", "right border of window used for TV computation (default: 3).") + + Argument("value").type_integer(0, 128) + + DataType::options(); REFERENCES - + "Kellner, E; Dhital, B; Kiselev, V.G & Reisert, M. " - "Gibbs-ringing artifact removal based on local subvoxel-shifts. " - "Magnetic Resonance in Medicine, 2016, 76, 1574–1581." - - + "Bautista, T; O’Muircheartaigh, J; Hajnal, JV; & Tournier, J-D. " - "Removal of Gibbs ringing artefacts for 3D acquisitions using subvoxel shifts. " - "Proc. ISMRM, 2021, 29, 3535."; + +"Kellner, E; Dhital, B; Kiselev, V.G & Reisert, M. " + "Gibbs-ringing artifact removal based on local subvoxel-shifts. " + "Magnetic Resonance in Medicine, 2016, 76, 1574–1581." + + "Bautista, T; O’Muircheartaigh, J; Hajnal, JV; & Tournier, J-D. " + "Removal of Gibbs ringing artefacts for 3D acquisitions using subvoxel shifts. " + "Proc. ISMRM, 2021, 29, 3535."; } - - - - -void run () -{ - const int nshifts = App::get_option_value ("nshifts", 20); - const int minW = App::get_option_value ("minW", 1); - const int maxW = App::get_option_value ("maxW", 3); +void run() { + const int nshifts = App::get_option_value("nshifts", 20); + const int minW = App::get_option_value("minW", 1); + const int maxW = App::get_option_value("maxW", 3); if (minW >= maxW) - throw Exception ("minW must be smaller than maxW"); + throw Exception("minW must be smaller than maxW"); - auto header = Header::open (argument[0]); + auto header = Header::open(argument[0]); auto in = header.get_image(); - header.datatype() = DataType::from_command_line (header.datatype().is_complex() ? DataType::CFloat32 : DataType::Float32); - auto out = Image::create (argument[1], header); - - int mode = get_option_value ("mode", 0); + header.datatype() = + DataType::from_command_line(header.datatype().is_complex() ? DataType::CFloat32 : DataType::Float32); + auto out = Image::create(argument[1], header); + int mode = get_option_value("mode", 0); - vector slice_axes = { 0, 1 }; - auto opt = get_options ("axes"); + vector slice_axes = {0, 1}; + auto opt = get_options("axes"); const bool axes_set_manually = opt.size(); if (opt.size()) { - vector axes = parse_ints (opt[0][0]); - if (axes == vector ({ 0, 1, 2 })) { + vector axes = parse_ints(opt[0][0]); + if (axes == vector({0, 1, 2})) { mode = 1; - } - else { + } else { if (axes.size() != 2) - throw Exception ("slice axes must be specified as a comma-separated 2-vector"); - if (size_t(std::max (axes[0], axes[1])) >= header.ndim()) - throw Exception ("slice axes must be within the dimensionality of the image"); + throw Exception("slice axes must be specified as a comma-separated 2-vector"); + if (size_t(std::max(axes[0], axes[1])) >= header.ndim()) + throw Exception("slice axes must be within the dimensionality of the image"); if (axes[0] == axes[1]) - throw Exception ("two independent slice axes must be specified"); - slice_axes = { size_t(axes[0]), size_t(axes[1]) }; + throw Exception("two independent slice axes must be specified"); + slice_axes = {size_t(axes[0]), size_t(axes[1])}; } } - auto slice_encoding_it = header.keyval().find ("SliceEncodingDirection"); + auto slice_encoding_it = header.keyval().find("SliceEncodingDirection"); if (slice_encoding_it != header.keyval().end()) { if (mode == 1) { - WARN ("running 3D volume-wise unringing, but image header contains \"SliceEncodingDirection\" field"); - WARN ("If data were acquired using multi-slice encoding, run in default 2D mode."); - } - else { + WARN("running 3D volume-wise unringing, but image header contains \"SliceEncodingDirection\" field"); + WARN("If data were acquired using multi-slice encoding, run in default 2D mode."); + } else { try { - const Eigen::Vector3d slice_encoding_axis_onehot = Axes::id2dir (slice_encoding_it->second); - vector auto_slice_axes = { 0, 0 }; + const Eigen::Vector3d slice_encoding_axis_onehot = Axes::id2dir(slice_encoding_it->second); + vector auto_slice_axes = {0, 0}; if (slice_encoding_axis_onehot[0]) - auto_slice_axes = { 1, 2 }; + auto_slice_axes = {1, 2}; else if (slice_encoding_axis_onehot[1]) - auto_slice_axes = { 0, 2 }; + auto_slice_axes = {0, 2}; else if (slice_encoding_axis_onehot[2]) - auto_slice_axes = { 0, 1 }; + auto_slice_axes = {0, 1}; else - throw Exception ("Fatal error: Invalid slice axis one-hot encoding [ " + str(slice_encoding_axis_onehot.transpose()) + " ]"); + throw Exception("Fatal error: Invalid slice axis one-hot encoding [ " + + str(slice_encoding_axis_onehot.transpose()) + " ]"); if (axes_set_manually) { if (slice_axes == auto_slice_axes) { - INFO ("User's manual selection of within-slice axes consistent with \"SliceEncodingDirection\" field in image header"); + INFO("User's manual selection of within-slice axes consistent with \"SliceEncodingDirection\" field in " + "image header"); } else { - WARN ("Within-slice axes set using -axes option will be used, but is inconsistent with SliceEncodingDirection field present in image header (" + slice_encoding_it->second + ")"); + WARN("Within-slice axes set using -axes option will be used, but is inconsistent with " + "SliceEncodingDirection field present in image header (" + + slice_encoding_it->second + ")"); } } else { if (slice_axes == auto_slice_axes) { - INFO ("\"SliceEncodingDirection\" field in image header is consistent with default selection of first two axes as being within-slice"); + INFO("\"SliceEncodingDirection\" field in image header is consistent with default selection of first two " + "axes as being within-slice"); } else { slice_axes = auto_slice_axes; - CONSOLE ("Using axes { " + str(slice_axes[0]) + ", " + str(slice_axes[1]) + " } for Gibbs ringing removal based on \"SliceEncodingDirection\" field in image header"); + CONSOLE("Using axes { " + str(slice_axes[0]) + ", " + str(slice_axes[1]) + + " } for Gibbs ringing removal based on \"SliceEncodingDirection\" field in image header"); } } } catch (...) { - WARN ("Invalid value for field \"SliceEncodingDirection\" in image header (" + slice_encoding_it->second + "); ignoring"); + WARN("Invalid value for field \"SliceEncodingDirection\" in image header (" + slice_encoding_it->second + + "); ignoring"); } } } - if (mode == 1) { - Degibbs::unring3D (in, out, minW, maxW, nshifts); + Degibbs::unring3D(in, out, minW, maxW, nshifts); return; } // build vector of outer axes: - vector outer_axes (header.ndim()); - std::iota (outer_axes.begin(), outer_axes.end(), 0); + vector outer_axes(header.ndim()); + std::iota(outer_axes.begin(), outer_axes.end(), 0); for (const auto axis : slice_axes) { - auto it = std::find (outer_axes.begin(), outer_axes.end(), axis); + auto it = std::find(outer_axes.begin(), outer_axes.end(), axis); if (it == outer_axes.end()) - throw Exception ("slice axis out of range!"); - outer_axes.erase (it); + throw Exception("slice axis out of range!"); + outer_axes.erase(it); } - ThreadedLoop ("performing 2D Gibbs ringing removal", in, outer_axes, slice_axes) - .run_outer (Degibbs::Unring2DFunctor (outer_axes, slice_axes, nshifts, minW, maxW, in, out)); + ThreadedLoop("performing 2D Gibbs ringing removal", in, outer_axes, slice_axes) + .run_outer(Degibbs::Unring2DFunctor(outer_axes, slice_axes, nshifts, minW, maxW, in, out)); } - diff --git a/cmd/mrdump.cpp b/cmd/mrdump.cpp index 9c4b357c85..7a19936ee6 100644 --- a/cmd/mrdump.cpp +++ b/cmd/mrdump.cpp @@ -14,105 +14,101 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "datatype.h" -#include "image.h" #include "adapter/replicate.h" #include "algo/loop.h" +#include "command.h" +#include "datatype.h" #include "file/ofstream.h" +#include "image.h" using namespace MR; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Print out the values within an image"; DESCRIPTION - + "If no destination file is specified, the voxel locations will be " - "printed to stdout."; + +"If no destination file is specified, the voxel locations will be " + "printed to stdout."; ARGUMENTS - + Argument ("input", "the input image.").type_image_in() - + Argument ("output", "the (optional) output text file.").type_file_out().optional(); + +Argument("input", "the input image.").type_image_in() + + Argument("output", "the (optional) output text file.").type_file_out().optional(); OPTIONS - + Option ("mask", "only write the image values within voxels specified by a mask image") - + Argument ("image").type_image_in(); + +Option("mask", "only write the image values within voxels specified by a mask image") + + Argument("image").type_image_in(); } - - -template -void write (Image& image, StreamType& out) -{ - for (auto l = Loop(image) (image); l; ++l) +template void write(Image &image, StreamType &out) { + for (auto l = Loop(image)(image); l; ++l) out << image.value() << "\n"; } -template -void write (Image image, Image& mask, StreamType& out) -{ +template void write(Image image, Image &mask, StreamType &out) { if (!mask.valid()) { - write (image, out); + write(image, out); return; } - Adapter::Replicate> replicate (mask, image); - for (auto l = Loop(image) (image, replicate); l; ++l) { + Adapter::Replicate> replicate(mask, image); + for (auto l = Loop(image)(image, replicate); l; ++l) { if (replicate.value()) out << image.value() << "\n"; } } - - -template -void write (Header& header, Image& mask, StreamType& out) -{ - switch (uint8_t(DataType(header.datatype())()) & ~(DataType::BigEndian | DataType::LittleEndian | DataType::Complex)) { - case DataType::Bit: case DataType::UInt8: case DataType::UInt16: case DataType::UInt32: - write (header.get_image(), mask, out); - break; - case DataType::Int8: case DataType::Int16: case DataType::Int32: - write (header.get_image(), mask, out); - break; - case DataType::UInt64: write (header.get_image(), mask, out); break; - case DataType::Int64: write (header.get_image(), mask, out); break; - case DataType::Float32: - if (header.datatype().is_complex()) - write (header.get_image(), mask, out); - else - write (header.get_image(), mask, out); - break; - case DataType::Float64: - if (header.datatype().is_complex()) - write (header.get_image(), mask, out); - else - write (header.get_image(), mask, out); - break; - default: - throw Exception ("Unknown data type: " + std::string(header.datatype().description()) + " (" + str(uint32_t(uint8_t(DataType(header.datatype())()))) + ")"); +template void write(Header &header, Image &mask, StreamType &out) { + switch (uint8_t(DataType(header.datatype())()) & + ~(DataType::BigEndian | DataType::LittleEndian | DataType::Complex)) { + case DataType::Bit: + case DataType::UInt8: + case DataType::UInt16: + case DataType::UInt32: + write(header.get_image(), mask, out); + break; + case DataType::Int8: + case DataType::Int16: + case DataType::Int32: + write(header.get_image(), mask, out); + break; + case DataType::UInt64: + write(header.get_image(), mask, out); + break; + case DataType::Int64: + write(header.get_image(), mask, out); + break; + case DataType::Float32: + if (header.datatype().is_complex()) + write(header.get_image(), mask, out); + else + write(header.get_image(), mask, out); + break; + case DataType::Float64: + if (header.datatype().is_complex()) + write(header.get_image(), mask, out); + else + write(header.get_image(), mask, out); + break; + default: + throw Exception("Unknown data type: " + std::string(header.datatype().description()) + " (" + + str(uint32_t(uint8_t(DataType(header.datatype())()))) + ")"); } } - - -void run () -{ - auto H = Header::open (argument[0]); +void run() { + auto H = Header::open(argument[0]); Image mask; - auto opt = get_options ("mask"); + auto opt = get_options("mask"); if (opt.size()) - mask = Image::open (opt[0][0]); + mask = Image::open(opt[0][0]); if (argument.size() == 2) { - File::OFStream out (argument[1]); - write (H, mask, out); + File::OFStream out(argument[1]); + write(H, mask, out); } else { - write (H, mask, std::cout); + write(H, mask, std::cout); } } diff --git a/cmd/mredit.cpp b/cmd/mredit.cpp index 577125f651..c7b98dc168 100644 --- a/cmd/mredit.cpp +++ b/cmd/mredit.cpp @@ -27,182 +27,162 @@ using namespace MR; using namespace App; - // TODO: // * Operate on mask images rather than arbitrary images? // * Remove capability to edit in-place - just deal with image swapping in the script? // * Tests - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Directly edit the intensities within an image from the command-line"; DESCRIPTION - + "A range of options are provided to enable direct editing of " - "voxel intensities based on voxel / real-space coordinates. " + +"A range of options are provided to enable direct editing of " + "voxel intensities based on voxel / real-space coordinates. " - "If only one image path is provided, the image will be edited in-place " - "(use at own risk); if input and output image paths are provided, the " - "output will contain the edited image, and the original image will not " - "be modified in any way."; + "If only one image path is provided, the image will be edited in-place " + "(use at own risk); if input and output image paths are provided, the " + "output will contain the edited image, and the original image will not " + "be modified in any way."; ARGUMENTS - + Argument ("input", "the input image").type_image_in() - + Argument ("output", "the (optional) output image").type_image_out().optional(); + +Argument("input", "the input image").type_image_in() + + Argument("output", "the (optional) output image").type_image_out().optional(); OPTIONS - + Option ("plane", "fill one or more planes on a particular image axis").allow_multiple() - + Argument ("axis").type_integer (0, 2) - + Argument ("coord").type_sequence_int() - + Argument ("value").type_float() - - + Option ("sphere", "draw a sphere with radius in mm").allow_multiple() - + Argument ("position").type_sequence_float() - + Argument ("radius").type_float() - + Argument ("value").type_float() + +Option("plane", "fill one or more planes on a particular image axis").allow_multiple() + + Argument("axis").type_integer(0, 2) + Argument("coord").type_sequence_int() + Argument("value").type_float() - + Option ("voxel", "change the image value within a single voxel").allow_multiple() - + Argument ("position").type_sequence_float() - + Argument ("value").type_float() + + Option("sphere", "draw a sphere with radius in mm").allow_multiple() + + Argument("position").type_sequence_float() + Argument("radius").type_float() + Argument("value").type_float() - + Option ("scanner", "indicate that coordinates are specified in scanner space, rather than as voxel coordinates"); + + Option("voxel", "change the image value within a single voxel").allow_multiple() + + Argument("position").type_sequence_float() + Argument("value").type_float() + + Option("scanner", "indicate that coordinates are specified in scanner space, rather than as voxel coordinates"); } - - -class Vox : public Eigen::Array3i -{ - public: - using Eigen::Array3i::Array3i; - Vox (const Eigen::Vector3d& p) : - Eigen::Array3i { int(std::round (p[0])), int(std::round (p[1])), int(std::round (p[2])) } { } - bool operator< (const Vox& i) const { - return (i[0] == (*this)[0] ? (i[1] == (*this)[1] ? (i[2] < (*this)[2]) : (i[1] < (*this)[1])) : (i[0] < (*this)[0])); - } +class Vox : public Eigen::Array3i { +public: + using Eigen::Array3i::Array3i; + Vox(const Eigen::Vector3d &p) : Eigen::Array3i{int(std::round(p[0])), int(std::round(p[1])), int(std::round(p[2]))} {} + bool operator<(const Vox &i) const { + return (i[0] == (*this)[0] ? (i[1] == (*this)[1] ? (i[2] < (*this)[2]) : (i[1] < (*this)[1])) + : (i[0] < (*this)[0])); + } }; +const Vox voxel_offsets[6] = {{0, 0, -1}, {0, 0, 1}, {0, -1, 0}, {0, 1, 0}, {-1, 0, 0}, {1, 0, 0}}; - -const Vox voxel_offsets[6] = { { 0, 0, -1}, - { 0, 0, 1}, - { 0, -1, 0}, - { 0, 1, 0}, - {-1, 0, 0}, - { 1, 0, 0} }; - - - -void run () -{ +void run() { bool inplace = (argument.size() == 1); - auto H = Header::open (argument[0]); - auto in = H.get_image (inplace); // Need to set read/write flag + auto H = Header::open(argument[0]); + auto in = H.get_image(inplace); // Need to set read/write flag Image out; if (inplace) { - out = Image (in); + out = Image(in); } else { - if (std::string(argument[1]) == std::string(argument[0])) // Not ideal test - could be different paths to the same file - throw Exception ("Do not provide same image as input and output; instad specify image to be edited in-place"); - out = Image::create (argument[1], H); - copy (in, out); + if (std::string(argument[1]) == + std::string(argument[0])) // Not ideal test - could be different paths to the same file + throw Exception("Do not provide same image as input and output; instad specify image to be edited in-place"); + out = Image::create(argument[1], H); + copy(in, out); } - Transform transform (H); - const bool scanner = get_options ("scanner").size(); + Transform transform(H); + const bool scanner = get_options("scanner").size(); if (scanner && H.ndim() < 3) - throw Exception ("Cannot specify scanner-space coordinates if image has less than 3 dimensions"); + throw Exception("Cannot specify scanner-space coordinates if image has less than 3 dimensions"); size_t operation_count = 0; - auto opt = get_options ("plane"); + auto opt = get_options("plane"); if (opt.size()) { if (H.ndim() != 3) - throw Exception ("-plane option only works for 3D images"); + throw Exception("-plane option only works for 3D images"); if (scanner) - throw Exception ("-plane option cannot be used with scanner-space coordinates"); + throw Exception("-plane option cannot be used with scanner-space coordinates"); } operation_count += opt.size(); for (auto p : opt) { const size_t axis = p[0]; - const auto coords = parse_ints (p[1]); + const auto coords = parse_ints(p[1]); const float value = p[2]; - const std::array loop_axes { { axis == 0 ? size_t(1) : size_t(0), axis == 2 ? size_t(1) : size_t(2) } }; + const std::array loop_axes{{axis == 0 ? size_t(1) : size_t(0), axis == 2 ? size_t(1) : size_t(2)}}; for (auto c : coords) { - out.index (axis) = c; - for (auto outer = Loop(loop_axes[0]) (out); outer; ++outer) { - for (auto inner = Loop(loop_axes[1]) (out); inner; ++inner) + out.index(axis) = c; + for (auto outer = Loop(loop_axes[0])(out); outer; ++outer) { + for (auto inner = Loop(loop_axes[1])(out); inner; ++inner) out.value() = value; } } } - opt = get_options ("sphere"); + opt = get_options("sphere"); if (opt.size() && H.ndim() != 3) - throw Exception ("-sphere option only works for 3D images"); + throw Exception("-sphere option only works for 3D images"); operation_count += opt.size(); for (auto s : opt) { - const auto position = parse_floats (s[0]); - Eigen::Vector3d centre_scannerspace (position[0], position[1], position[2]); + const auto position = parse_floats(s[0]); + Eigen::Vector3d centre_scannerspace(position[0], position[1], position[2]); const default_type radius = s[1]; const float value = s[2]; if (position.size() != 3) - throw Exception ("Centre of sphere must be defined using 3 comma-separated values"); - Eigen::Vector3d centre_voxelspace (centre_scannerspace); + throw Exception("Centre of sphere must be defined using 3 comma-separated values"); + Eigen::Vector3d centre_voxelspace(centre_scannerspace); if (scanner) centre_voxelspace = transform.scanner2voxel * centre_scannerspace; else centre_scannerspace = transform.voxel2scanner * centre_voxelspace; std::set processed; vector to_expand; - const Vox seed_voxel (centre_voxelspace); - processed.insert (seed_voxel); - to_expand.push_back (seed_voxel); + const Vox seed_voxel(centre_voxelspace); + processed.insert(seed_voxel); + to_expand.push_back(seed_voxel); while (to_expand.size()) { - const Vox v (to_expand.back()); + const Vox v(to_expand.back()); to_expand.pop_back(); const Eigen::Vector3d v_scanner = transform.voxel2scanner * v.matrix().cast(); const default_type distance = (v_scanner - centre_scannerspace).norm(); if (distance < radius) { - if (!is_out_of_bounds (H, v)) { - assign_pos_of (v).to (out); + if (!is_out_of_bounds(H, v)) { + assign_pos_of(v).to(out); out.value() = value; } for (size_t i = 0; i != 6; ++i) { - const Vox v_adj (v + voxel_offsets[i]); - if (processed.find (v_adj) == processed.end()) { - processed.insert (v_adj); - to_expand.push_back (v_adj); + const Vox v_adj(v + voxel_offsets[i]); + if (processed.find(v_adj) == processed.end()) { + processed.insert(v_adj); + to_expand.push_back(v_adj); } } } } } - opt = get_options ("voxel"); + opt = get_options("voxel"); operation_count += opt.size(); for (auto v : opt) { - const auto position = parse_floats (v[0]); + const auto position = parse_floats(v[0]); const float value = v[1]; if (position.size() != H.ndim()) - throw Exception ("Image has " + str(H.ndim()) + " dimensions, but -voxel option position " + std::string(v[0]) + " provides only " + str(position.size()) + " coordinates"); + throw Exception("Image has " + str(H.ndim()) + " dimensions, but -voxel option position " + std::string(v[0]) + + " provides only " + str(position.size()) + " coordinates"); if (scanner) { - Eigen::Vector3d p (position[0], position[1], position[2]); + Eigen::Vector3d p(position[0], position[1], position[2]); p = transform.scanner2voxel * p; - const Vox voxel (p); - assign_pos_of (voxel).to (out); + const Vox voxel(p); + assign_pos_of(voxel).to(out); for (size_t axis = 3; axis != out.ndim(); ++axis) { - if (std::round (position[axis]) != position[axis]) - throw Exception ("Non-spatial coordinates provided using -voxel option must be provided as integers"); + if (std::round(position[axis]) != position[axis]) + throw Exception("Non-spatial coordinates provided using -voxel option must be provided as integers"); out.index(axis) = position[axis]; } } else { for (size_t axis = 0; axis != out.ndim(); ++axis) { - if (std::round (position[axis]) != position[axis]) - throw Exception ("Voxel coordinates provided using -voxel option must be provided as integers"); + if (std::round(position[axis]) != position[axis]) + throw Exception("Voxel coordinates provided using -voxel option must be provided as integers"); out.index(axis) = position[axis]; } } @@ -211,9 +191,9 @@ void run () if (!operation_count) { if (inplace) { - WARN ("No edits specified; image will be unaffected"); + WARN("No edits specified; image will be unaffected"); } else { - WARN ("No edits specified; output image will be copy of input"); + WARN("No edits specified; output image will be copy of input"); } } } diff --git a/cmd/mrfilter.cpp b/cmd/mrfilter.cpp index 30d0ef30f7..3b4f43dddb 100644 --- a/cmd/mrfilter.cpp +++ b/cmd/mrfilter.cpp @@ -17,323 +17,315 @@ #include #include "command.h" -#include "image.h" -#include "math/fft.h" #include "filter/base.h" #include "filter/gradient.h" -#include "filter/normalise.h" #include "filter/median.h" +#include "filter/normalise.h" #include "filter/smooth.h" #include "filter/zclean.h" - +#include "image.h" +#include "math/fft.h" using namespace MR; using namespace App; - -const char* filters[] = { "fft", "gradient", "median", "smooth", "normalise", "zclean", NULL }; - - -const OptionGroup FFTOption = OptionGroup ("Options for FFT filter") - - + Option ("axes", "the axes along which to apply the Fourier Transform. " - "By default, the transform is applied along the three spatial axes. " - "Provide as a comma-separate list of axis indices.") - + Argument ("list").type_sequence_int() - - + Option ("inverse", "apply the inverse FFT") - - + Option ("magnitude", "output a magnitude image rather than a complex-valued image") - - + Option ("rescale", "rescale values so that inverse FFT recovers original values") - - + Option ("centre_zero", "re-arrange the FFT results so that the zero-frequency component " - "appears in the centre of the image, rather than at the edges"); - - - -const OptionGroup GradientOption = OptionGroup ("Options for gradient filter") - - + Option ("stdev", "the standard deviation of the Gaussian kernel used to " - "smooth the input image (in mm). The image is smoothed to reduced large " - "spurious gradients caused by noise. Use this option to override " - "the default stdev of 1 voxel. This can be specified either as a single " - "value to be used for all 3 axes, or as a comma-separated list of " - "3 values, one for each axis.") - + Argument ("sigma").type_sequence_float() - - + Option ("magnitude", "output the gradient magnitude, rather " - "than the default x,y,z components") - - + Option ("scanner", "define the gradient with respect to the scanner coordinate " - "frame of reference."); - - -const OptionGroup MedianOption = OptionGroup ("Options for median filter") - - + Option ("extent", "specify extent of median filtering neighbourhood in voxels. " - "This can be specified either as a single value to be used for all 3 axes, " - "or as a comma-separated list of 3 values, one for each axis (default: 3x3x3).") - + Argument ("size").type_sequence_int(); - -const OptionGroup NormaliseOption = OptionGroup ("Options for normalisation filter") - - + Option ("extent", "specify extent of normalisation filtering neighbourhood in voxels. " - "This can be specified either as a single value to be used for all 3 axes, " - "or as a comma-separated list of 3 values, one for each axis (default: 3x3x3).") - + Argument ("size").type_sequence_int(); - - -const OptionGroup SmoothOption = OptionGroup ("Options for smooth filter") - - + Option ("stdev", "apply Gaussian smoothing with the specified standard deviation. " - "The standard deviation is defined in mm (Default 1 voxel). " - "This can be specified either as a single value to be used for all axes, " - "or as a comma-separated list of the stdev for each axis.") - + Argument ("mm").type_sequence_float() - - + Option ("fwhm", "apply Gaussian smoothing with the specified full-width half maximum. " - "The FWHM is defined in mm (Default 1 voxel * 2.3548). " - "This can be specified either as a single value to be used for all axes, " - "or as a comma-separated list of the FWHM for each axis.") - + Argument ("mm").type_sequence_float() - - + Option ("extent", "specify the extent (width) of kernel size in voxels. " - "This can be specified either as a single value to be used for all axes, " - "or as a comma-separated list of the extent for each axis. " - "The default extent is 2 * ceil(2.5 * stdev / voxel_size) - 1.") - + Argument ("voxels").type_sequence_int(); - -const OptionGroup ZcleanOption = OptionGroup ("Options for zclean filter") -+ Option ("zupper", "define high intensity outliers: default: 2.5") - + Argument ("num").type_float(0.1, std::numeric_limits::infinity()) -+ Option ("zlower", "define low intensity outliers: default: 2.5") - + Argument ("num").type_float(0.1, std::numeric_limits::infinity()) -+ Option ("bridge", "number of voxels to gap to fill holes in mask: default: 4") - + Argument ("num").type_integer(0) -+ Option ("maskin", "initial mask that defines the maximum spatial extent and the region from " - "which to smaple the intensity range.") - + Argument ("image").type_image_in() -+ Option ("maskout", "Output a refined mask based on a spatially coherent region with normal intensity range.") - + Argument ("image").type_image_out(); - - -void usage () -{ - AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au), David Raffelt (david.raffelt@florey.edu.au) and J-Donald Tournier (jdtournier@gmail.com)"; +const char *filters[] = {"fft", "gradient", "median", "smooth", "normalise", "zclean", NULL}; + +const OptionGroup FFTOption = OptionGroup("Options for FFT filter") + + + Option("axes", + "the axes along which to apply the Fourier Transform. " + "By default, the transform is applied along the three spatial axes. " + "Provide as a comma-separate list of axis indices.") + + Argument("list").type_sequence_int() + + + Option("inverse", "apply the inverse FFT") + + + Option("magnitude", "output a magnitude image rather than a complex-valued image") + + + Option("rescale", "rescale values so that inverse FFT recovers original values") + + + Option("centre_zero", + "re-arrange the FFT results so that the zero-frequency component " + "appears in the centre of the image, rather than at the edges"); + +const OptionGroup GradientOption = OptionGroup("Options for gradient filter") + + + Option("stdev", + "the standard deviation of the Gaussian kernel used to " + "smooth the input image (in mm). The image is smoothed to reduced large " + "spurious gradients caused by noise. Use this option to override " + "the default stdev of 1 voxel. This can be specified either as a single " + "value to be used for all 3 axes, or as a comma-separated list of " + "3 values, one for each axis.") + + Argument("sigma").type_sequence_float() + + + Option("magnitude", + "output the gradient magnitude, rather " + "than the default x,y,z components") + + + Option("scanner", + "define the gradient with respect to the scanner coordinate " + "frame of reference."); + +const OptionGroup MedianOption = + OptionGroup("Options for median filter") + + + Option("extent", + "specify extent of median filtering neighbourhood in voxels. " + "This can be specified either as a single value to be used for all 3 axes, " + "or as a comma-separated list of 3 values, one for each axis (default: 3x3x3).") + + Argument("size").type_sequence_int(); + +const OptionGroup NormaliseOption = + OptionGroup("Options for normalisation filter") + + + Option("extent", + "specify extent of normalisation filtering neighbourhood in voxels. " + "This can be specified either as a single value to be used for all 3 axes, " + "or as a comma-separated list of 3 values, one for each axis (default: 3x3x3).") + + Argument("size").type_sequence_int(); + +const OptionGroup SmoothOption = OptionGroup("Options for smooth filter") + + + Option("stdev", + "apply Gaussian smoothing with the specified standard deviation. " + "The standard deviation is defined in mm (Default 1 voxel). " + "This can be specified either as a single value to be used for all axes, " + "or as a comma-separated list of the stdev for each axis.") + + Argument("mm").type_sequence_float() + + + Option("fwhm", + "apply Gaussian smoothing with the specified full-width half maximum. " + "The FWHM is defined in mm (Default 1 voxel * 2.3548). " + "This can be specified either as a single value to be used for all axes, " + "or as a comma-separated list of the FWHM for each axis.") + + Argument("mm").type_sequence_float() + + + Option("extent", + "specify the extent (width) of kernel size in voxels. " + "This can be specified either as a single value to be used for all axes, " + "or as a comma-separated list of the extent for each axis. " + "The default extent is 2 * ceil(2.5 * stdev / voxel_size) - 1.") + + Argument("voxels").type_sequence_int(); + +const OptionGroup ZcleanOption = + OptionGroup("Options for zclean filter") + Option("zupper", "define high intensity outliers: default: 2.5") + + Argument("num").type_float(0.1, std::numeric_limits::infinity()) + + Option("zlower", "define low intensity outliers: default: 2.5") + + Argument("num").type_float(0.1, std::numeric_limits::infinity()) + + Option("bridge", "number of voxels to gap to fill holes in mask: default: 4") + Argument("num").type_integer(0) + + Option("maskin", + "initial mask that defines the maximum spatial extent and the region from " + "which to smaple the intensity range.") + + Argument("image").type_image_in() + + Option("maskout", "Output a refined mask based on a spatially coherent region with normal intensity range.") + + Argument("image").type_image_out(); + +void usage() { + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au), David Raffelt (david.raffelt@florey.edu.au) and J-Donald " + "Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Perform filtering operations on 3D / 4D MR images"; DESCRIPTION - + "The available filters are: fft, gradient, median, smooth, normalise, zclean." - + "Each filter has its own unique set of optional parameters." - + "For 4D images, each 3D volume is processed independently."; + +"The available filters are: fft, gradient, median, smooth, normalise, zclean." + + "Each filter has its own unique set of optional parameters." + + "For 4D images, each 3D volume is processed independently."; ARGUMENTS - + Argument ("input", "the input image.").type_image_in () - + Argument ("filter", "the type of filter to be applied").type_choice (filters) - + Argument ("output", "the output image.").type_image_out (); + +Argument("input", "the input image.").type_image_in() + + Argument("filter", "the type of filter to be applied").type_choice(filters) + + Argument("output", "the output image.").type_image_out(); OPTIONS - + FFTOption - + GradientOption - + MedianOption - + NormaliseOption - + SmoothOption - + ZcleanOption - + Stride::Options; + +FFTOption + GradientOption + MedianOption + NormaliseOption + SmoothOption + ZcleanOption + Stride::Options; } - -void run () { +void run() { const size_t filter_index = argument[1]; switch (filter_index) { - // FFT - case 0: - { - // FIXME Had to use cdouble throughout; seems to fail at compile time even trying to - // convert between cfloat and cdouble... - auto input = Image::open (argument[0]); - - vector axes = { 0, 1, 2 }; - auto opt = get_options ("axes"); - if (opt.size()) { - axes = parse_ints (opt[0][0]); - for (const auto axis : axes) - if (axis >= input.ndim()) - throw Exception ("axis provided with -axes option is out of range"); - } - const int direction = get_options ("inverse").size() ? FFTW_BACKWARD : FFTW_FORWARD; - const bool centre_FFT = get_options ("centre_zero").size(); - const bool magnitude = get_options ("magnitude").size(); - - Header header = input; - Stride::set_from_command_line (header); - header.datatype() = magnitude ? DataType::Float32 : DataType::CFloat64; - auto output = Image::create (argument[2], header); - double scale = 1.0; - - Image in (input), out; - for (size_t n = 0; n < axes.size(); ++n) { - scale *= in.size(axes[n]); - if (n >= (axes.size()-1) && !magnitude) { - out = output; - } - else { - if (!out.valid()) - out = Image::scratch (input); - } - - Math::FFT (in, out, axes[n], direction, centre_FFT); - - in = out; + // FFT + case 0: { + // FIXME Had to use cdouble throughout; seems to fail at compile time even trying to + // convert between cfloat and cdouble... + auto input = Image::open(argument[0]); + + vector axes = {0, 1, 2}; + auto opt = get_options("axes"); + if (opt.size()) { + axes = parse_ints(opt[0][0]); + for (const auto axis : axes) + if (axis >= input.ndim()) + throw Exception("axis provided with -axes option is out of range"); + } + const int direction = get_options("inverse").size() ? FFTW_BACKWARD : FFTW_FORWARD; + const bool centre_FFT = get_options("centre_zero").size(); + const bool magnitude = get_options("magnitude").size(); + + Header header = input; + Stride::set_from_command_line(header); + header.datatype() = magnitude ? DataType::Float32 : DataType::CFloat64; + auto output = Image::create(argument[2], header); + double scale = 1.0; + + Image in(input), out; + for (size_t n = 0; n < axes.size(); ++n) { + scale *= in.size(axes[n]); + if (n >= (axes.size() - 1) && !magnitude) { + out = output; + } else { + if (!out.valid()) + out = Image::scratch(input); } - if (magnitude) { - ThreadedLoop (out).run ( - [](decltype(out)& a, decltype(output)& b) { a.value() = abs(cdouble (b.value())); }, - output, out); - } - if (get_options ("rescale").size()) { - scale = std::sqrt (scale); - ThreadedLoop (out).run ( - [&scale](decltype(out)& a) { a.value() /= scale; }, - output); - } + Math::FFT(in, out, axes[n], direction, centre_FFT); - break; + in = out; + } + + if (magnitude) { + ThreadedLoop(out).run( + [](decltype(out) &a, decltype(output) &b) { a.value() = abs(cdouble(b.value())); }, output, out); + } + if (get_options("rescale").size()) { + scale = std::sqrt(scale); + ThreadedLoop(out).run([&scale](decltype(out) &a) { a.value() /= scale; }, output); } - // Gradient - case 1: - { - auto input = Image::open (argument[0]); - Filter::Gradient filter (input, get_options ("magnitude").size()); - - vector stdev; - auto opt = get_options ("stdev"); - if (opt.size()) { - stdev = parse_floats (opt[0][0]); - for (size_t i = 0; i < stdev.size(); ++i) - if (stdev[i] < 0.0) - throw Exception ("the Gaussian stdev values cannot be negative"); - if (stdev.size() != 1 && stdev.size() != 3) - throw Exception ("unexpected number of elements specified in Gaussian stdev"); - } else { - stdev.resize (3, 0.0); - for (size_t dim = 0; dim != 3; ++dim) - stdev[dim] = filter.spacing (dim); - } - filter.compute_wrt_scanner (get_options ("scanner").size() ? true : false); - filter.set_message (std::string("applying ") + std::string(argument[1]) + " filter to image " + std::string(argument[0])); - Stride::set_from_command_line (filter); - filter.set_stdev (stdev); - auto output = Image::create (argument[2], filter); - filter (input, output); break; + } + + // Gradient + case 1: { + auto input = Image::open(argument[0]); + Filter::Gradient filter(input, get_options("magnitude").size()); + + vector stdev; + auto opt = get_options("stdev"); + if (opt.size()) { + stdev = parse_floats(opt[0][0]); + for (size_t i = 0; i < stdev.size(); ++i) + if (stdev[i] < 0.0) + throw Exception("the Gaussian stdev values cannot be negative"); + if (stdev.size() != 1 && stdev.size() != 3) + throw Exception("unexpected number of elements specified in Gaussian stdev"); + } else { + stdev.resize(3, 0.0); + for (size_t dim = 0; dim != 3; ++dim) + stdev[dim] = filter.spacing(dim); } + filter.compute_wrt_scanner(get_options("scanner").size() ? true : false); + filter.set_message(std::string("applying ") + std::string(argument[1]) + " filter to image " + + std::string(argument[0])); + Stride::set_from_command_line(filter); + filter.set_stdev(stdev); + auto output = Image::create(argument[2], filter); + filter(input, output); + break; + } + + // Median + case 2: { + auto input = Image::open(argument[0]); + Filter::Median filter(input); - // Median - case 2: - { - auto input = Image::open (argument[0]); - Filter::Median filter (input); - - auto opt = get_options ("extent"); - if (opt.size()) - filter.set_extent (parse_ints (opt[0][0])); - filter.set_message (std::string("applying ") + std::string(argument[1]) + " filter to image " + std::string(argument[0])); - Stride::set_from_command_line (filter); - - auto output = Image::create (argument[2], filter); - filter (input, output); - break; - } - - // Smooth - case 3: - { - auto input = Image::open (argument[0]); - Filter::Smooth filter (input); - - auto opt = get_options ("stdev"); - const bool stdev_supplied = opt.size(); + auto opt = get_options("extent"); + if (opt.size()) + filter.set_extent(parse_ints(opt[0][0])); + filter.set_message(std::string("applying ") + std::string(argument[1]) + " filter to image " + + std::string(argument[0])); + Stride::set_from_command_line(filter); + + auto output = Image::create(argument[2], filter); + filter(input, output); + break; + } + + // Smooth + case 3: { + auto input = Image::open(argument[0]); + Filter::Smooth filter(input); + + auto opt = get_options("stdev"); + const bool stdev_supplied = opt.size(); + if (stdev_supplied) + filter.set_stdev(parse_floats(opt[0][0])); + opt = get_options("fwhm"); + if (opt.size()) { if (stdev_supplied) - filter.set_stdev (parse_floats (opt[0][0])); - opt = get_options ("fwhm"); - if (opt.size()) { - if (stdev_supplied) - throw Exception ("the stdev and FWHM options are mutually exclusive."); - vector stdevs = parse_floats((opt[0][0])); - for (size_t d = 0; d < stdevs.size(); ++d) - stdevs[d] = stdevs[d] / 2.3548; //convert FWHM to stdev - filter.set_stdev (stdevs); - } - opt = get_options ("extent"); - if (opt.size()) - filter.set_extent (parse_ints (opt[0][0])); - filter.set_message (std::string("applying ") + std::string(argument[1]) + " filter to image " + std::string(argument[0])); - Stride::set_from_command_line (filter); - - auto output = Image::create (argument[2], filter); - threaded_copy (input, output); - filter (output); - break; + throw Exception("the stdev and FWHM options are mutually exclusive."); + vector stdevs = parse_floats((opt[0][0])); + for (size_t d = 0; d < stdevs.size(); ++d) + stdevs[d] = stdevs[d] / 2.3548; // convert FWHM to stdev + filter.set_stdev(stdevs); } + opt = get_options("extent"); + if (opt.size()) + filter.set_extent(parse_ints(opt[0][0])); + filter.set_message(std::string("applying ") + std::string(argument[1]) + " filter to image " + + std::string(argument[0])); + Stride::set_from_command_line(filter); + + auto output = Image::create(argument[2], filter); + threaded_copy(input, output); + filter(output); + break; + } - // Normalisation - case 4: - { - auto input = Image::open (argument[0]); - Filter::Normalise filter (input); - - auto opt = get_options ("extent"); - if (opt.size()) - filter.set_extent (parse_ints (opt[0][0])); - filter.set_message (std::string("applying ") + std::string(argument[1]) + " filter to image " + std::string(argument[0]) + "..."); - Stride::set_from_command_line (filter); - - auto output = Image::create (argument[2], filter); - filter (input, output); - break; - } - - // Zclean - case 5: - { - auto input = Image::open (argument[0]); - Filter::ZClean filter (input); - - auto opt = get_options ("maskin"); - if (!opt.size()) - throw Exception (std::string(argument[1]) + " filter requires initial mask"); - Image maskin = Image::open (opt[0][0]); - check_dimensions (maskin, input, 0, 3); - - filter.set_message (std::string("applying ") + std::string(argument[1]) + " filter to image " + std::string(argument[0]) + "..."); - Stride::set_from_command_line (filter); - - filter.set_voxels_to_bridge (get_option_value ("bridge", 4)); - float zlower = get_option_value ("zlower", 2.5); - float zupper = get_option_value ("zupper", 2.5); - filter.set_zlim (zlower, zupper); - - auto output = Image::create (argument[2], filter); - filter (input, maskin, output); - - opt = get_options ("maskout"); - if (opt.size()) { - auto maskout = Image::create (opt[0][0], filter.mask); - threaded_copy (filter.mask, maskout); - } - break; + // Normalisation + case 4: { + auto input = Image::open(argument[0]); + Filter::Normalise filter(input); + + auto opt = get_options("extent"); + if (opt.size()) + filter.set_extent(parse_ints(opt[0][0])); + filter.set_message(std::string("applying ") + std::string(argument[1]) + " filter to image " + + std::string(argument[0]) + "..."); + Stride::set_from_command_line(filter); + + auto output = Image::create(argument[2], filter); + filter(input, output); + break; + } + + // Zclean + case 5: { + auto input = Image::open(argument[0]); + Filter::ZClean filter(input); + + auto opt = get_options("maskin"); + if (!opt.size()) + throw Exception(std::string(argument[1]) + " filter requires initial mask"); + Image maskin = Image::open(opt[0][0]); + check_dimensions(maskin, input, 0, 3); + + filter.set_message(std::string("applying ") + std::string(argument[1]) + " filter to image " + + std::string(argument[0]) + "..."); + Stride::set_from_command_line(filter); + + filter.set_voxels_to_bridge(get_option_value("bridge", 4)); + float zlower = get_option_value("zlower", 2.5); + float zupper = get_option_value("zupper", 2.5); + filter.set_zlim(zlower, zupper); + + auto output = Image::create(argument[2], filter); + filter(input, maskin, output); + + opt = get_options("maskout"); + if (opt.size()) { + auto maskout = Image::create(opt[0][0], filter.mask); + threaded_copy(filter.mask, maskout); } + break; + } - default: - assert (0); - break; + default: + assert(0); + break; } } diff --git a/cmd/mrgrid.cpp b/cmd/mrgrid.cpp index 1e2e274a35..23b4364d19 100644 --- a/cmd/mrgrid.cpp +++ b/cmd/mrgrid.cpp @@ -14,174 +14,205 @@ * For more details, see http://www.mrtrix.org/. */ -#include +#include "adapter/regrid.h" +#include "algo/copy.h" #include "command.h" -#include "image.h" #include "filter/resize.h" #include "filter/reslice.h" -#include "interp/nearest.h" -#include "interp/linear.h" +#include "image.h" #include "interp/cubic.h" +#include "interp/linear.h" +#include "interp/nearest.h" #include "interp/sinc.h" #include "progressbar.h" -#include "algo/copy.h" -#include "adapter/regrid.h" - +#include using namespace MR; using namespace App; -const char* interp_choices[] = { "nearest", "linear", "cubic", "sinc", NULL }; -const char* operation_choices[] = { "regrid", "crop", "pad", NULL }; - +const char *interp_choices[] = {"nearest", "linear", "cubic", "sinc", NULL}; +const char *operation_choices[] = {"regrid", "crop", "pad", NULL}; -void usage () -{ - AUTHOR = "Max Pietsch (maximilian.pietsch@kcl.ac.uk) & David Raffelt (david.raffelt@florey.edu.au) & Robert E. Smith (robert.smith@florey.edu.au)"; +void usage() { + AUTHOR = "Max Pietsch (maximilian.pietsch@kcl.ac.uk) & David Raffelt (david.raffelt@florey.edu.au) & Robert E. Smith " + "(robert.smith@florey.edu.au)"; - SYNOPSIS = "Modify the grid of an image without interpolation (cropping or padding) or by regridding to an image grid with modified orientation, location and or resolution. The image content remains in place in real world coordinates."; + SYNOPSIS = + "Modify the grid of an image without interpolation (cropping or padding) or by regridding to an image grid with " + "modified orientation, location and or resolution. The image content remains in place in real world coordinates."; DESCRIPTION - + "- regrid: This operation performs changes of the voxel grid that require interpolation of the image such as changing the resolution or location and orientation of the voxel grid. " - "If the image is down-sampled, the appropriate smoothing is automatically applied using Gaussian smoothing unless nearest neighbour interpolation is selected or oversample is changed explicitly. The resolution can only be changed for spatial dimensions. " - + "- crop: The image extent after cropping, can be specified either manually for each axis dimensions, or via a mask or reference image. " - "The image can be cropped to the extent of a mask. " - "This is useful for axially-acquired brain images, where the image size can be reduced by a factor of 2 by removing the empty space on either side of the brain. Note that cropping does not extend the image beyond the original FOV unless explicitly specified (via -crop_unbound or negative -axis extent)." - + "- pad: Analogously to cropping, padding increases the FOV of an image without image interpolation. Pad and crop can be performed simultaneously by specifying signed specifier argument values to the -axis option." - + "This command encapsulates and extends the functionality of the superseded commands 'mrpad', 'mrcrop' and 'mrresize'. Note the difference in -axis convention used for 'mrcrop' and 'mrpad' (see -axis option description)."; + +"- regrid: This operation performs changes of the voxel grid that require interpolation of the image such as " + "changing the resolution or location and orientation of the voxel grid. " + "If the image is down-sampled, the appropriate smoothing is automatically applied using Gaussian smoothing unless " + "nearest neighbour interpolation is selected or oversample is changed explicitly. The resolution can only be " + "changed for spatial dimensions. " + + "- crop: The image extent after cropping, can be specified either manually for each axis dimensions, or via a " + "mask or reference image. " + "The image can be cropped to the extent of a mask. " + "This is useful for axially-acquired brain images, where the image size can be reduced by a factor of 2 by " + "removing the empty space on either side of the brain. Note that cropping does not extend the image beyond the " + "original FOV unless explicitly specified (via -crop_unbound or negative -axis extent)." + + "- pad: Analogously to cropping, padding increases the FOV of an image without image interpolation. Pad and crop " + "can be performed simultaneously by specifying signed specifier argument values to the -axis option." + + "This command encapsulates and extends the functionality of the superseded commands 'mrpad', 'mrcrop' and " + "'mrresize'. Note the difference in -axis convention used for 'mrcrop' and 'mrpad' (see -axis option " + "description)."; EXAMPLES - + Example ("Crop and pad the first axis", - "mrgrid in.mif crop -axis 0 10,-5 out.mif", - "This removes 10 voxels on the lower and pads with 5 on the upper bound, which is equivalent to " - "padding with the negated specifier (mrgrid in.mif pad -axis 0 -10,5 out.mif).") - - + Example ("Right-pad the image to the number of voxels of a reference image", - "mrgrid in.mif pad -as ref.mif -all_axes -axis 3 0,0 out.mif -fill nan", - "This pads the image on the upper bound of all axes except for the volume dimension. " - "The headers of in.mif and ref.mif are ignored and the output image uses NAN values to fill in voxels outside the original range of in.mif.") - - + Example ("Regrid and interpolate to match the voxel grid of a reference image", - "mrgrid in.mif regrid -template ref.mif -scale 1,1,0.5 out.mif -fill nan", - "The -template instructs to regrid in.mif to match the voxel grid of ref.mif (voxel size, grid orientation and voxel centres). " - "The -scale option overwrites the voxel scaling factor yielding voxel sizes in the third dimension that are " - "twice as coarse as those of the template image."); - + +Example("Crop and pad the first axis", + "mrgrid in.mif crop -axis 0 10,-5 out.mif", + "This removes 10 voxels on the lower and pads with 5 on the upper bound, which is equivalent to " + "padding with the negated specifier (mrgrid in.mif pad -axis 0 -10,5 out.mif).") + + + Example("Right-pad the image to the number of voxels of a reference image", + "mrgrid in.mif pad -as ref.mif -all_axes -axis 3 0,0 out.mif -fill nan", + "This pads the image on the upper bound of all axes except for the volume dimension. " + "The headers of in.mif and ref.mif are ignored and the output image uses NAN values to fill in voxels " + "outside the original range of in.mif.") + + + + Example( + "Regrid and interpolate to match the voxel grid of a reference image", + "mrgrid in.mif regrid -template ref.mif -scale 1,1,0.5 out.mif -fill nan", + "The -template instructs to regrid in.mif to match the voxel grid of ref.mif (voxel size, grid orientation " + "and voxel centres). " + "The -scale option overwrites the voxel scaling factor yielding voxel sizes in the third dimension that are " + "twice as coarse as those of the template image."); ARGUMENTS - + Argument ("input", "input image to be regridded.").type_image_in () - + Argument ("operation", "the operation to be performed, one of: " + join(operation_choices, ", ") + ".").type_choice (operation_choices) - + Argument ("output", "the output image.").type_image_out (); + +Argument("input", "input image to be regridded.").type_image_in() + + Argument("operation", "the operation to be performed, one of: " + join(operation_choices, ", ") + ".") + .type_choice(operation_choices) + + Argument("output", "the output image.").type_image_out(); OPTIONS - + OptionGroup ("Regridding options (involves image interpolation, applied to spatial axes only)") - + Option ("template", "match the input image grid (voxel spacing, image size, header transformation) to that of a reference image. " - "The image resolution relative to the template image can be changed with one of -size, -voxel, -scale." ) - + Argument ("image").type_image_in () - - + Option ("size", "define the size (number of voxels) in each spatial dimension for the output image. " - "This should be specified as a comma-separated list.") - + Argument ("dims").type_sequence_int() - - + Option ("voxel", "define the new voxel size for the output image. " - "This can be specified either as a single value to be used for all spatial dimensions, " - "or as a comma-separated list of the size for each voxel dimension.") - + Argument ("size").type_sequence_float() - - + Option ("scale", "scale the image resolution by the supplied factor. " - "This can be specified either as a single value to be used for all dimensions, " - "or as a comma-separated list of scale factors for each dimension.") - + Argument ("factor").type_sequence_float() - - + Option ("interp", "set the interpolation method to use when reslicing (choices: nearest, linear, cubic, sinc. Default: cubic).") - + Argument ("method").type_choice (interp_choices) - - + Option ("oversample", - "set the amount of over-sampling (in the target space) to perform when regridding. This is particularly " - "relevant when downsamping a high-resolution image to a low-resolution image, to avoid aliasing artefacts. " - "This can consist of a single integer, or a comma-separated list of 3 integers if different oversampling " - "factors are desired along the different axes. Default is determined from ratio of voxel dimensions (disabled " - "for nearest-neighbour interpolation).") - + Argument ("factor").type_sequence_int() - - + OptionGroup ("Pad and crop options (no image interpolation is performed, header transformation is adjusted)") - + Option ("as", "pad or crop the input image on the upper bound to match the specified reference image grid. " - "This operation ignores differences in image transformation between input and reference image.") - + Argument ("reference image").type_image_in () - - + Option ("uniform", "pad or crop the input image by a uniform number of voxels on all sides") - + Argument ("number").type_integer () - - + Option ("mask", "crop the input image according to the spatial extent of a mask image. " - "The mask must share a common voxel grid with the input image but differences in image transformations are " - "ignored. Note that even though only 3 dimensions are cropped when using a mask, the bounds are computed by " - "checking the extent for all dimensions. " - "Note that by default a gap of 1 voxel is left at all edges of the image to allow valid trilinear interpolation. " - "This gap can be modified with the -uniform option but by default it does not extend beyond the FOV unless -crop_unbound is used.") - + Argument ("image", "the mask image. ").type_image_in() - - + Option ("crop_unbound", "Allow padding beyond the original FOV when cropping.") - - + Option ("axis", "pad or crop the input image along the provided axis (defined by index). The specifier argument " - "defines the number of voxels added or removed on the lower or upper end of the axis (-axis index delta_lower,delta_upper) " - "or acts as a voxel selection range (-axis index start:stop). In both modes, values are relative to the input image " - "(overriding all other extent-specifying options). Negative delta specifier values trigger the inverse operation " - "(pad instead of crop and vice versa) and negative range specifier trigger padding. " - "Note that the deprecated commands 'mrcrop' and 'mrpad' used range-based and delta-based -axis indices, respectively." - ).allow_multiple() - + Argument ("index").type_integer (0) - + Argument ("spec").type_text() - - + Option ("all_axes", "Crop or pad all, not just spatial axes.") - - + OptionGroup ("General options") - + Option ("fill", "Use number as the out of bounds value. nan, inf and -inf are valid arguments. (Default: 0.0)") - + Argument ("number").type_float () - - + Stride::Options - + DataType::options(); + +OptionGroup("Regridding options (involves image interpolation, applied to spatial axes only)") + + Option( + "template", + "match the input image grid (voxel spacing, image size, header transformation) to that of a reference image. " + "The image resolution relative to the template image can be changed with one of -size, -voxel, -scale.") + + Argument("image").type_image_in() + + + Option("size", + "define the size (number of voxels) in each spatial dimension for the output image. " + "This should be specified as a comma-separated list.") + + Argument("dims").type_sequence_int() + + + Option("voxel", + "define the new voxel size for the output image. " + "This can be specified either as a single value to be used for all spatial dimensions, " + "or as a comma-separated list of the size for each voxel dimension.") + + Argument("size").type_sequence_float() + + + Option("scale", + "scale the image resolution by the supplied factor. " + "This can be specified either as a single value to be used for all dimensions, " + "or as a comma-separated list of scale factors for each dimension.") + + Argument("factor").type_sequence_float() + + + Option("interp", + "set the interpolation method to use when reslicing (choices: nearest, linear, cubic, sinc. Default: " + "cubic).") + + Argument("method").type_choice(interp_choices) + + + Option( + "oversample", + "set the amount of over-sampling (in the target space) to perform when regridding. This is particularly " + "relevant when downsamping a high-resolution image to a low-resolution image, to avoid aliasing artefacts. " + "This can consist of a single integer, or a comma-separated list of 3 integers if different oversampling " + "factors are desired along the different axes. Default is determined from ratio of voxel dimensions " + "(disabled " + "for nearest-neighbour interpolation).") + + Argument("factor").type_sequence_int() + + + OptionGroup("Pad and crop options (no image interpolation is performed, header transformation is adjusted)") + + Option("as", + "pad or crop the input image on the upper bound to match the specified reference image grid. " + "This operation ignores differences in image transformation between input and reference image.") + + Argument("reference image").type_image_in() + + + Option("uniform", "pad or crop the input image by a uniform number of voxels on all sides") + + Argument("number").type_integer() + + + + Option( + "mask", + "crop the input image according to the spatial extent of a mask image. " + "The mask must share a common voxel grid with the input image but differences in image transformations are " + "ignored. Note that even though only 3 dimensions are cropped when using a mask, the bounds are computed by " + "checking the extent for all dimensions. " + "Note that by default a gap of 1 voxel is left at all edges of the image to allow valid trilinear " + "interpolation. " + "This gap can be modified with the -uniform option but by default it does not extend beyond the FOV unless " + "-crop_unbound is used.") + + Argument("image", "the mask image. ").type_image_in() + + + Option("crop_unbound", "Allow padding beyond the original FOV when cropping.") + + + Option("axis", + "pad or crop the input image along the provided axis (defined by index). The specifier argument " + "defines the number of voxels added or removed on the lower or upper end of the axis (-axis index " + "delta_lower,delta_upper) " + "or acts as a voxel selection range (-axis index start:stop). In both modes, values are relative to the " + "input image " + "(overriding all other extent-specifying options). Negative delta specifier values trigger the inverse " + "operation " + "(pad instead of crop and vice versa) and negative range specifier trigger padding. " + "Note that the deprecated commands 'mrcrop' and 'mrpad' used range-based and delta-based -axis indices, " + "respectively.") + .allow_multiple() + + Argument("index").type_integer(0) + Argument("spec").type_text() + + + Option("all_axes", "Crop or pad all, not just spatial axes.") + + + OptionGroup("General options") + + Option("fill", "Use number as the out of bounds value. nan, inf and -inf are valid arguments. (Default: 0.0)") + + Argument("number").type_float() + + + Stride::Options + DataType::options(); } - -void run () { - auto input_header = Header::open (argument[0]); +void run() { + auto input_header = Header::open(argument[0]); const int op = argument[1]; // Out of bounds value default_type out_of_bounds_value = 0.0; - auto opt = get_options ("fill"); + auto opt = get_options("fill"); if (opt.size()) out_of_bounds_value = opt[0][0]; if (op == 0) { // regrid INFO("operation: " + str(operation_choices[op])); - Filter::Resize regrid_filter (input_header); + Filter::Resize regrid_filter(input_header); regrid_filter.set_out_of_bounds_value(out_of_bounds_value); size_t resize_option_count = 0; size_t template_option_count = 0; - int interp = 2; // cubic - opt = get_options ("interp"); + int interp = 2; // cubic + opt = get_options("interp"); if (opt.size()) { interp = opt[0][0]; } // over-sampling vector oversample = Adapter::AutoOverSample; - opt = get_options ("oversample"); + opt = get_options("oversample"); if (opt.size()) { - oversample = parse_ints (opt[0][0]); + oversample = parse_ints(opt[0][0]); } Header template_header; - opt = get_options ("template"); + opt = get_options("template"); if (opt.size()) { template_header = Header::open(opt[0][0]); if (template_header.ndim() < 3) - throw Exception ("the template image requires at least 3 spatial dimensions"); - add_line (regrid_filter.keyval()["comments"], std::string ("regridded to template image \"" + template_header.name() + "\"")); - for (auto i=0; i<3; ++i) { + throw Exception("the template image requires at least 3 spatial dimensions"); + add_line(regrid_filter.keyval()["comments"], + std::string("regridded to template image \"" + template_header.name() + "\"")); + for (auto i = 0; i < 3; ++i) { regrid_filter.spacing(i) = template_header.spacing(i); regrid_filter.size(i) = template_header.size(i); } @@ -189,112 +220,114 @@ void run () { ++template_option_count; } - regrid_filter.set_interp_type (interp); - regrid_filter.set_oversample (oversample); + regrid_filter.set_interp_type(interp); + regrid_filter.set_oversample(oversample); vector scale; - opt = get_options ("scale"); + opt = get_options("scale"); if (opt.size()) { - scale = parse_floats (opt[0][0]); + scale = parse_floats(opt[0][0]); if (scale.size() == 1) - scale.resize (3, scale[0]); - regrid_filter.set_scale_factor (scale); + scale.resize(3, scale[0]); + regrid_filter.set_scale_factor(scale); ++resize_option_count; } vector image_size; - opt = get_options ("size"); + opt = get_options("size"); if (opt.size()) { - image_size = parse_ints (opt[0][0]); - regrid_filter.set_size (image_size); + image_size = parse_ints(opt[0][0]); + regrid_filter.set_size(image_size); ++resize_option_count; } vector voxel_size; - opt = get_options ("voxel"); + opt = get_options("voxel"); if (opt.size()) { - voxel_size = parse_floats (opt[0][0]); + voxel_size = parse_floats(opt[0][0]); if (voxel_size.size() == 1) - voxel_size.resize (3, voxel_size[0]); - regrid_filter.set_voxel_size (voxel_size); + voxel_size.resize(3, voxel_size[0]); + regrid_filter.set_voxel_size(voxel_size); ++resize_option_count; } if (!resize_option_count and !template_option_count) - throw Exception ("please use either the -scale, -voxel, -resolution or -template option to regrid the image"); + throw Exception("please use either the -scale, -voxel, -resolution or -template option to regrid the image"); if (resize_option_count > 1) - throw Exception ("only a single method can be used to resize the image (image resolution, voxel size or scale factor)"); + throw Exception( + "only a single method can be used to resize the image (image resolution, voxel size or scale factor)"); - Header output_header (regrid_filter); - Stride::set_from_command_line (output_header); + Header output_header(regrid_filter); + Stride::set_from_command_line(output_header); if (interp == 0) - output_header.datatype() = DataType::from_command_line (input_header.datatype()); + output_header.datatype() = DataType::from_command_line(input_header.datatype()); else - output_header.datatype() = DataType::from_command_line (DataType::from ()); - auto output = Image::create (argument[2], output_header); + output_header.datatype() = DataType::from_command_line(DataType::from()); + auto output = Image::create(argument[2], output_header); auto input = input_header.get_image(); - regrid_filter (input, output); + regrid_filter(input, output); } else { // crop or pad const bool do_crop = op == 1; std::string message = do_crop ? "cropping image" : "padding image"; INFO("operation: " + str(operation_choices[op])); - if (get_options ("crop_unbound").size() && !do_crop) + if (get_options("crop_unbound").size() && !do_crop) throw Exception("-crop_unbound only applies only to the crop operation"); - const size_t nd = get_options ("nd").size() ? input_header.ndim() : 3; + const size_t nd = get_options("nd").size() ? input_header.ndim() : 3; - vector> bounds (input_header.ndim(), vector (2)); + vector> bounds(input_header.ndim(), vector(2)); for (size_t axis = 0; axis < input_header.ndim(); axis++) { bounds[axis][0] = 0; - bounds[axis][1] = input_header.size (axis) - 1; + bounds[axis][1] = input_header.size(axis) - 1; } size_t crop_pad_option_count = 0; - opt = get_options ("mask"); + opt = get_options("mask"); if (opt.size()) { - if (!do_crop) throw Exception("padding with -mask option is not supported"); + if (!do_crop) + throw Exception("padding with -mask option is not supported"); INFO("cropping to mask"); ++crop_pad_option_count; - auto mask = Image::open (opt[0][0]); - check_dimensions (input_header, mask, 0, 3); + auto mask = Image::open(opt[0][0]); + check_dimensions(input_header, mask, 0, 3); for (size_t axis = 0; axis != 3; ++axis) { - bounds[axis][0] = input_header.size (axis); + bounds[axis][0] = input_header.size(axis); bounds[axis][1] = 0; } - struct BoundsCheck { - vector>& overall_bounds; + struct BoundsCheck { + vector> &overall_bounds; vector> bounds; - BoundsCheck (vector>& overall_bounds) : overall_bounds (overall_bounds), bounds (overall_bounds) { } - ~BoundsCheck () { + BoundsCheck(vector> &overall_bounds) : overall_bounds(overall_bounds), bounds(overall_bounds) {} + ~BoundsCheck() { for (size_t axis = 0; axis != 3; ++axis) { - overall_bounds[axis][0] = std::min (bounds[axis][0], overall_bounds[axis][0]); - overall_bounds[axis][1] = std::max (bounds[axis][1], overall_bounds[axis][1]); + overall_bounds[axis][0] = std::min(bounds[axis][0], overall_bounds[axis][0]); + overall_bounds[axis][1] = std::max(bounds[axis][1], overall_bounds[axis][1]); } } - void operator() (const decltype(mask)& m) { + void operator()(const decltype(mask) &m) { if (m.value()) { for (size_t axis = 0; axis != 3; ++axis) { - bounds[axis][0] = std::min (bounds[axis][0], m.index(axis)); - bounds[axis][1] = std::max (bounds[axis][1], m.index(axis)); + bounds[axis][0] = std::min(bounds[axis][0], m.index(axis)); + bounds[axis][1] = std::max(bounds[axis][1], m.index(axis)); } } } }; - ThreadedLoop (mask).run (BoundsCheck (bounds), mask); + ThreadedLoop(mask).run(BoundsCheck(bounds), mask); for (size_t axis = 0; axis != 3; ++axis) { - if ((input_header.size (axis) - 1) != bounds[axis][1] or bounds[axis][0] != 0) - INFO ("cropping to mask changes axis " + str(axis) + " extent from 0:" + str(input_header.size (axis) - 1) + - " to " + str(bounds[axis][0]) + ":" + str(bounds[axis][1])); + if ((input_header.size(axis) - 1) != bounds[axis][1] or bounds[axis][0] != 0) + INFO("cropping to mask changes axis " + str(axis) + " extent from 0:" + str(input_header.size(axis) - 1) + + " to " + str(bounds[axis][0]) + ":" + str(bounds[axis][1])); } - if (!get_options ("uniform").size()) { - INFO ("uniformly padding around mask by 1 voxel"); + if (!get_options("uniform").size()) { + INFO("uniformly padding around mask by 1 voxel"); // margin of 1 voxel around mask for (size_t axis = 0; axis != 3; ++axis) { bounds[axis][0] -= 1; @@ -303,10 +336,10 @@ void run () { } } - opt = get_options ("as"); + opt = get_options("as"); if (opt.size()) { if (crop_pad_option_count) - throw Exception (str(operation_choices[op]) + " can be performed using either a mask or a template image"); + throw Exception(str(operation_choices[op]) + " can be performed using either a mask or a template image"); ++crop_pad_option_count; Header template_header = Header::open(opt[0][0]); @@ -315,83 +348,93 @@ void run () { if (axis >= template_header.ndim()) { if (do_crop) bounds[axis][1] = 0; - } else { + } else { if (do_crop) - bounds[axis][1] = std::min (bounds[axis][1], template_header.size(axis) - 1); + bounds[axis][1] = std::min(bounds[axis][1], template_header.size(axis) - 1); else - bounds[axis][1] = std::max (bounds[axis][1], template_header.size(axis) - 1); + bounds[axis][1] = std::max(bounds[axis][1], template_header.size(axis) - 1); } } } - opt = get_options ("uniform"); + opt = get_options("uniform"); if (opt.size()) { ++crop_pad_option_count; ssize_t val = opt[0][0]; - INFO ("uniformly " + str(do_crop ? "cropping" : "padding") + " by " + str(val) + " voxels"); + INFO("uniformly " + str(do_crop ? "cropping" : "padding") + " by " + str(val) + " voxels"); for (size_t axis = 0; axis < nd; axis++) { bounds[axis][0] += do_crop ? val : -val; bounds[axis][1] += do_crop ? -val : val; } } - if (do_crop && !get_options ("crop_unbound").size()) { - opt = get_options ("axis"); + if (do_crop && !get_options("crop_unbound").size()) { + opt = get_options("axis"); std::set ignore; for (size_t i = 0; i != opt.size(); ++i) ignore.insert(opt[i][0]); for (size_t axis = 0; axis != 3; ++axis) { if (bounds[axis][0] < 0 || bounds[axis][1] > input_header.size(axis) - 1) { if (ignore.find(axis) == ignore.end()) - INFO ("operation: crop without -crop_unbound: restricting padding on axis " + str(axis) + " to valid FOV " + - str(std::max (0, bounds[axis][0])) + ":" + str(std::min (bounds[axis][1], input_header.size(axis) - 1))); - bounds[axis][0] = std::max (0, bounds[axis][0]); - bounds[axis][1] = std::min (bounds[axis][1], input_header.size(axis) - 1); + INFO("operation: crop without -crop_unbound: restricting padding on axis " + str(axis) + " to valid FOV " + + str(std::max(0, bounds[axis][0])) + ":" + + str(std::min(bounds[axis][1], input_header.size(axis) - 1))); + bounds[axis][0] = std::max(0, bounds[axis][0]); + bounds[axis][1] = std::min(bounds[axis][1], input_header.size(axis) - 1); } } } - opt = get_options ("axis"); // overrides image bounds set by other options + opt = get_options("axis"); // overrides image bounds set by other options for (size_t i = 0; i != opt.size(); ++i) { ++crop_pad_option_count; - const size_t axis = opt[i][0]; - if (axis >= input_header.ndim()) - throw Exception ("-axis " + str(axis) + " larger than image dimensions (" + str(input_header.ndim()) + ")"); + const size_t axis = opt[i][0]; + if (axis >= input_header.ndim()) + throw Exception("-axis " + str(axis) + " larger than image dimensions (" + str(input_header.ndim()) + ")"); std::string spec = str(opt[i][1]); std::string::size_type start = 0, end; end = spec.find_first_of(":", start); if (end == std::string::npos) { // spec = delta_lower,delta_upper - vector delta; // 0: not changed, > 0: pad, < 0: crop - try { delta = parse_ints (opt[i][1]); } - catch (Exception& E) { Exception (E, "-axis " + str(axis) + ": can't parse delta specifier \"" + spec + "\""); } + vector delta; // 0: not changed, > 0: pad, < 0: crop + try { + delta = parse_ints(opt[i][1]); + } catch (Exception &E) { + Exception(E, "-axis " + str(axis) + ": can't parse delta specifier \"" + spec + "\""); + } if (delta.size() != 2) - throw Exception ("-axis " + str(axis) + ": can't parse delta specifier \"" + spec + "\""); + throw Exception("-axis " + str(axis) + ": can't parse delta specifier \"" + spec + "\""); bounds[axis][0] = do_crop ? delta[0] : -delta[0]; - bounds[axis][1] = input_header.size (axis) - 1 + (do_crop ? -delta[1] : delta[1]); + bounds[axis][1] = input_header.size(axis) - 1 + (do_crop ? -delta[1] : delta[1]); } else { // spec = delta_lower:delta_upper - std::string token (strip (spec.substr (start, end-start))); - try { bounds[axis][0] = std::stoi(token); } - catch (Exception& E) { throw Exception (E, "-axis " + str(axis) + ": can't parse integer sequence specifier \"" + spec + "\""); } - token = strip (spec.substr (end+1)); - if (lowercase (token) == "end" || token.size() == 0) + std::string token(strip(spec.substr(start, end - start))); + try { + bounds[axis][0] = std::stoi(token); + } catch (Exception &E) { + throw Exception(E, "-axis " + str(axis) + ": can't parse integer sequence specifier \"" + spec + "\""); + } + token = strip(spec.substr(end + 1)); + if (lowercase(token) == "end" || token.size() == 0) bounds[axis][1] = input_header.size(axis) - 1; else { - try { bounds[axis][1] = std::stoi(token); } - catch (Exception& E) { throw Exception (E, "-axis " + str(axis) + ": can't parse integer sequence specifier \"" + spec + "\""); } + try { + bounds[axis][1] = std::stoi(token); + } catch (Exception &E) { + throw Exception(E, "-axis " + str(axis) + ": can't parse integer sequence specifier \"" + spec + "\""); + } } } } for (size_t axis = 0; axis != 3; ++axis) { if (bounds[axis][1] < bounds[axis][0]) - throw Exception ("axis " + str(axis) + " is empty: (" + str(bounds[axis][0]) + ":" + str(bounds[axis][1]) + ")"); + throw Exception("axis " + str(axis) + " is empty: (" + str(bounds[axis][0]) + ":" + str(bounds[axis][1]) + ")"); } if (crop_pad_option_count == 0) - throw Exception ("no crop or pad option supplied"); + throw Exception("no crop or pad option supplied"); - vector from (input_header.ndim()); - vector size (input_header.ndim()); + vector from(input_header.ndim()); + vector size(input_header.ndim()); for (size_t axis = 0; axis < input_header.ndim(); axis++) { from[axis] = bounds[axis][0]; size[axis] = bounds[axis][1] - from[axis] + 1; @@ -399,24 +442,24 @@ void run () { size_t changed_axes = 0; for (size_t axis = 0; axis < nd; axis++) { - if (bounds[axis][0] != 0 || input_header.size (axis) != size[axis]) { + if (bounds[axis][0] != 0 || input_header.size(axis) != size[axis]) { changed_axes++; - CONSOLE("changing axis " + str(axis) + " extent from 0:" + str(input_header.size (axis) - 1) + - " (n="+str(input_header.size (axis))+") to " + str(bounds[axis][0]) + ":" + str(bounds[axis][1]) + - " (n=" + str(size[axis]) + ")"); + CONSOLE("changing axis " + str(axis) + " extent from 0:" + str(input_header.size(axis) - 1) + + " (n=" + str(input_header.size(axis)) + ") to " + str(bounds[axis][0]) + ":" + str(bounds[axis][1]) + + " (n=" + str(size[axis]) + ")"); } } if (!changed_axes) - WARN ("no axes were changed"); + WARN("no axes were changed"); auto input = input_header.get_image(); - auto regridded = Adapter::make (input, from, size, out_of_bounds_value); - Header output_header (regridded); - output_header.datatype() = DataType::from_command_line (DataType::from ()); - Stride::set_from_command_line (output_header); + auto regridded = Adapter::make(input, from, size, out_of_bounds_value); + Header output_header(regridded); + output_header.datatype() = DataType::from_command_line(DataType::from()); + Stride::set_from_command_line(output_header); - auto output = Image::create (argument[2], output_header); - threaded_copy_with_progress_message (message.c_str(), regridded, output); + auto output = Image::create(argument[2], output_header); + threaded_copy_with_progress_message(message.c_str(), regridded, output); } } diff --git a/cmd/mrhistmatch.cpp b/cmd/mrhistmatch.cpp index d66a3e82cd..c0bb882b1e 100644 --- a/cmd/mrhistmatch.cpp +++ b/cmd/mrhistmatch.cpp @@ -26,122 +26,119 @@ #include "algo/histogram.h" #include "algo/loop.h" - using namespace MR; using namespace App; -const char* choices[] = { "scale", "linear", "nonlinear", nullptr }; +const char *choices[] = {"scale", "linear", "nonlinear", nullptr}; -void usage () { +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Modify the intensities of one image to match the histogram of another"; ARGUMENTS - + Argument ("type", "type of histogram matching to perform; options are: " + join(choices, ",")).type_choice (choices) - + Argument ("input", "the input image to be modified").type_image_in () - + Argument ("target", "the input image from which to derive the target histogram").type_image_in() - + Argument ("output", "the output image").type_image_out(); + +Argument("type", "type of histogram matching to perform; options are: " + join(choices, ",")).type_choice(choices) + + Argument("input", "the input image to be modified").type_image_in() + + Argument("target", "the input image from which to derive the target histogram").type_image_in() + + Argument("output", "the output image").type_image_out(); OPTIONS - + OptionGroup ("Image masking options") - + Option ("mask_input", "only generate input histogram based on a specified binary mask image") - + Argument ("image").type_image_in () - + Option ("mask_target", "only generate target histogram based on a specified binary mask image") - + Argument ("image").type_image_in () - - + OptionGroup ("Non-linear histogram matching options") - + Option ("bins", "the number of bins to use to generate the histograms") - + Argument ("num").type_integer (2); + +OptionGroup("Image masking options") + + Option("mask_input", "only generate input histogram based on a specified binary mask image") + + Argument("image").type_image_in() + + Option("mask_target", "only generate target histogram based on a specified binary mask image") + + Argument("image").type_image_in() + + OptionGroup("Non-linear histogram matching options") + + Option("bins", "the number of bins to use to generate the histograms") + Argument("num").type_integer(2); REFERENCES - + "* If using inverse contrast normalization for inter-modal (DWI - T1) registration:\n" - "Bhushan, C.; Haldar, J. P.; Choi, S.; Joshi, A. A.; Shattuck, D. W. & Leahy, R. M. " - "Co-registration and distortion correction of diffusion and anatomical images based on inverse contrast normalization. " - "NeuroImage, 2015, 115, 269-280"; - + +"* If using inverse contrast normalization for inter-modal (DWI - T1) registration:\n" + "Bhushan, C.; Haldar, J. P.; Choi, S.; Joshi, A. A.; Shattuck, D. W. & Leahy, R. M. " + "Co-registration and distortion correction of diffusion and anatomical images based on inverse contrast " + "normalization. " + "NeuroImage, 2015, 115, 269-280"; } - - - -void match_linear (Image& input, - Image& target, - Image& mask_input, - Image& mask_target, - const bool estimate_intercept) -{ +void match_linear(Image &input, + Image &target, + Image &mask_input, + Image &mask_target, + const bool estimate_intercept) { vector input_data, target_data; { - ProgressBar progress ("Loading & sorting image data", 4); + ProgressBar progress("Loading & sorting image data", 4); - auto fill = [] (Image& image, Image& mask) { + auto fill = [](Image &image, Image &mask) { vector data; if (mask.valid()) { - Adapter::Replicate> mask_replicate (mask, image); - for (auto l = Loop(image) (image, mask_replicate); l; ++l) { - if (mask_replicate.value() && std::isfinite (static_cast(image.value()))) - data.push_back (image.value()); + Adapter::Replicate> mask_replicate(mask, image); + for (auto l = Loop(image)(image, mask_replicate); l; ++l) { + if (mask_replicate.value() && std::isfinite(static_cast(image.value()))) + data.push_back(image.value()); } } else { - for (auto l = Loop(image) (image); l; ++l) { - if (std::isfinite (static_cast(image.value()))) - data.push_back (image.value()); + for (auto l = Loop(image)(image); l; ++l) { + if (std::isfinite(static_cast(image.value()))) + data.push_back(image.value()); } } return data; }; - input_data = fill (input, mask_input); + input_data = fill(input, mask_input); ++progress; - target_data = fill (target, mask_target); + target_data = fill(target, mask_target); ++progress; - std::sort (input_data.begin(), input_data.end()); + std::sort(input_data.begin(), input_data.end()); ++progress; - std::sort (target_data.begin(), target_data.end()); + std::sort(target_data.begin(), target_data.end()); } // Ax=b // A: Input data - // x: Model parameters; in the "scale" case, it's a single multiplier; if "linear", include a column of ones and estimate an intercept - // b: Output data (or actually, interpolated histogram-matched output data) - Eigen::Matrix input_matrix (input_data.size(), estimate_intercept ? 2 : 1); - Eigen::Matrix output_vector (input_data.size()); - for (size_t input_index = 0; input_index != input_data.size()-1; ++input_index) { + // x: Model parameters; in the "scale" case, it's a single multiplier; if "linear", include a column of ones and + // estimate an intercept b: Output data (or actually, interpolated histogram-matched output data) + Eigen::Matrix input_matrix(input_data.size(), + estimate_intercept ? 2 : 1); + Eigen::Matrix output_vector(input_data.size()); + for (size_t input_index = 0; input_index != input_data.size() - 1; ++input_index) { input_matrix(input_index, 0) = input_data[input_index]; - const default_type output_position = (target_data.size()-1) * (default_type(input_index) / default_type(input_data.size()-1)); - const size_t target_index_lower = std::floor (output_position); + const default_type output_position = + (target_data.size() - 1) * (default_type(input_index) / default_type(input_data.size() - 1)); + const size_t target_index_lower = std::floor(output_position); const default_type mu = output_position - default_type(target_index_lower); - output_vector[input_index] = ((1.0-mu)*target_data[target_index_lower]) + (mu*target_data[target_index_lower+1]); + output_vector[input_index] = + ((1.0 - mu) * target_data[target_index_lower]) + (mu * target_data[target_index_lower + 1]); } - input_matrix(input_data.size()-1, 0) = input_data.back(); - output_vector[input_data.size()-1] = target_data.back(); + input_matrix(input_data.size() - 1, 0) = input_data.back(); + output_vector[input_data.size() - 1] = target_data.back(); if (estimate_intercept) - input_matrix.col(1).fill (1.0f); + input_matrix.col(1).fill(1.0f); - auto parameters = (input_matrix.transpose() * input_matrix).llt().solve(input_matrix.transpose() * output_vector).eval(); + auto parameters = + (input_matrix.transpose() * input_matrix).llt().solve(input_matrix.transpose() * output_vector).eval(); - Header H (input); + Header H(input); H.datatype() = DataType::Float32; H.datatype().set_byte_order_native(); H.keyval()["mrhistmatch_scale"] = str(parameters[0]); if (estimate_intercept) { - CONSOLE ("Estimated linear transform is: " + str(parameters[0]) + "x + " + str(parameters[1])); + CONSOLE("Estimated linear transform is: " + str(parameters[0]) + "x + " + str(parameters[1])); H.keyval()["mrhistmatch_offset"] = str(parameters[1]); - auto output = Image::create (argument[3], H); - for (auto l = Loop("Writing output image data", input) (input, output); l; ++l) { + auto output = Image::create(argument[3], H); + for (auto l = Loop("Writing output image data", input)(input, output); l; ++l) { if (std::isfinite(static_cast(input.value()))) { - output.value() = parameters[0]*input.value() + parameters[1]; + output.value() = parameters[0] * input.value() + parameters[1]; } else { output.value() = input.value(); } } } else { - CONSOLE ("Estimated scale factor is " + str(parameters[0])); - auto output = Image::create (argument[3], H); - for (auto l = Loop("Writing output image data", input) (input, output); l; ++l) { + CONSOLE("Estimated scale factor is " + str(parameters[0])); + auto output = Image::create(argument[3], H); + for (auto l = Loop("Writing output image data", input)(input, output); l; ++l) { if (std::isfinite(static_cast(input.value()))) { output.value() = input.value() * parameters[0]; } else { @@ -151,74 +148,63 @@ void match_linear (Image& input, } } +void match_nonlinear( + Image &input, Image &target, Image &mask_input, Image &mask_target, const size_t nbins) { + Algo::Histogram::Calibrator calib_input(nbins, true); + Algo::Histogram::calibrate(calib_input, input, mask_input); + INFO("Input histogram ranges from " + str(calib_input.get_min()) + " to " + str(calib_input.get_max()) + "; using " + + str(calib_input.get_num_bins()) + " bins"); + Algo::Histogram::Data hist_input = Algo::Histogram::generate(calib_input, input, mask_input); - - -void match_nonlinear (Image& input, - Image& target, - Image& mask_input, - Image& mask_target, - const size_t nbins) -{ - Algo::Histogram::Calibrator calib_input (nbins, true); - Algo::Histogram::calibrate (calib_input, input, mask_input); - INFO ("Input histogram ranges from " + str(calib_input.get_min()) + " to " + str(calib_input.get_max()) + "; using " + str(calib_input.get_num_bins()) + " bins"); - Algo::Histogram::Data hist_input = Algo::Histogram::generate (calib_input, input, mask_input); - - Algo::Histogram::Calibrator calib_target (nbins, true); - Algo::Histogram::calibrate (calib_target, target, mask_target); - INFO ("Target histogram ranges from " + str(calib_target.get_min()) + " to " + str(calib_target.get_max()) + "; using " + str(calib_target.get_num_bins()) + " bins"); - Algo::Histogram::Data hist_target = Algo::Histogram::generate (calib_target, target, mask_target); + Algo::Histogram::Calibrator calib_target(nbins, true); + Algo::Histogram::calibrate(calib_target, target, mask_target); + INFO("Target histogram ranges from " + str(calib_target.get_min()) + " to " + str(calib_target.get_max()) + + "; using " + str(calib_target.get_num_bins()) + " bins"); + Algo::Histogram::Data hist_target = Algo::Histogram::generate(calib_target, target, mask_target); // Non-linear intensity mapping determined within this class - Algo::Histogram::Matcher matcher (hist_input, hist_target); + Algo::Histogram::Matcher matcher(hist_input, hist_target); - Header H (input); + Header H(input); H.datatype() = DataType::Float32; H.datatype().set_byte_order_native(); - auto output = Image::create (argument[3], H); - for (auto l = Loop("Writing output data", input) (input, output); l; ++l) { - if (std::isfinite (static_cast(input.value()))) { - output.value() = matcher (input.value()); + auto output = Image::create(argument[3], H); + for (auto l = Loop("Writing output data", input)(input, output); l; ++l) { + if (std::isfinite(static_cast(input.value()))) { + output.value() = matcher(input.value()); } else { output.value() = input.value(); } } } - - - - -void run () -{ - auto input = Image::open (argument[1]); - auto target = Image::open (argument[2]); +void run() { + auto input = Image::open(argument[1]); + auto target = Image::open(argument[2]); Image mask_input, mask_target; - auto opt = get_options ("mask_input"); + auto opt = get_options("mask_input"); if (opt.size()) { - mask_input = Image::open (opt[0][0]); - check_dimensions (input, mask_input, 0, 3); + mask_input = Image::open(opt[0][0]); + check_dimensions(input, mask_input, 0, 3); } - opt = get_options ("mask_target"); + opt = get_options("mask_target"); if (opt.size()) { - mask_target = Image::open (opt[0][0]); - check_dimensions (target, mask_target, 0, 3); + mask_target = Image::open(opt[0][0]); + check_dimensions(target, mask_target, 0, 3); } switch (int(argument[0])) { - case 0: // Scale - match_linear (input, target, mask_input, mask_target, false); - break; - case 1: // Linear - match_linear (input, target, mask_input, mask_target, true); - break; - case 2: // Non-linear - match_nonlinear (input, target, mask_input, mask_target, get_option_value ("bins", 0)); - break; - default: - throw Exception ("Undefined histogram matching type"); + case 0: // Scale + match_linear(input, target, mask_input, mask_target, false); + break; + case 1: // Linear + match_linear(input, target, mask_input, mask_target, true); + break; + case 2: // Non-linear + match_nonlinear(input, target, mask_input, mask_target, get_option_value("bins", 0)); + break; + default: + throw Exception("Undefined histogram matching type"); } } - diff --git a/cmd/mrhistogram.cpp b/cmd/mrhistogram.cpp index b2e17294c7..8c5147660f 100644 --- a/cmd/mrhistogram.cpp +++ b/cmd/mrhistogram.cpp @@ -14,139 +14,122 @@ * For more details, see http://www.mrtrix.org/. */ +#include "algo/histogram.h" #include "command.h" #include "header.h" #include "image.h" -#include "algo/histogram.h" using namespace MR; using namespace App; +void usage() { + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; -void usage () -{ -AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; - -SYNOPSIS = "Generate a histogram of image intensities"; + SYNOPSIS = "Generate a histogram of image intensities"; -ARGUMENTS - + Argument ("image", "the input image from which the histogram will be computed").type_image_in() - + Argument ("hist", "the output histogram file").type_file_out(); + ARGUMENTS + +Argument("image", "the input image from which the histogram will be computed").type_image_in() + + Argument("hist", "the output histogram file").type_file_out(); -OPTIONS - + Algo::Histogram::Options - - + OptionGroup ("Additional options for mrhistogram") - + Option ("allvolumes", "generate one histogram across all image volumes, rather than one per image volume"); + OPTIONS + +Algo::Histogram::Options + + OptionGroup("Additional options for mrhistogram") + + Option("allvolumes", "generate one histogram across all image volumes, rather than one per image volume"); } +class Volume_loop { +public: + Volume_loop(Image &in) : image(in), is_4D(in.ndim() == 4), status(true) { + if (is_4D) + image.index(3) = 0; + } - -class Volume_loop { - public: - Volume_loop (Image& in) : - image (in), - is_4D (in.ndim() == 4), - status (true) - { - if (is_4D) - image.index(3) = 0; - } - - void operator++ () - { - if (is_4D) { - image.index(3)++; - } else { - assert (status); - status = false; - } - } - operator bool() const - { - if (is_4D) - return (image.index(3) >= 0 && image.index(3) < image.size(3)); - else - return status; + void operator++() { + if (is_4D) { + image.index(3)++; + } else { + assert(status); + status = false; } + } + operator bool() const { + if (is_4D) + return (image.index(3) >= 0 && image.index(3) < image.size(3)); + else + return status; + } - private: - Image& image; - const bool is_4D; - bool status; +private: + Image ℑ + const bool is_4D; + bool status; }; - - -template -void run_volume (Functor& functor, Image& data, Image& mask) -{ +template void run_volume(Functor &functor, Image &data, Image &mask) { if (mask.valid()) { - for (auto l = Loop(0,3) (data, mask); l; ++l) { + for (auto l = Loop(0, 3)(data, mask); l; ++l) { if (mask.value()) - functor (float(data.value())); + functor(float(data.value())); } } else { - for (auto l = Loop(0,3) (data); l; ++l) - functor (float(data.value())); + for (auto l = Loop(0, 3)(data); l; ++l) + functor(float(data.value())); } } +void run() { - -void run () -{ - - auto header = Header::open (argument[0]); + auto header = Header::open(argument[0]); if (header.ndim() > 4) - throw Exception ("mrhistogram is not designed to handle images greater than 4D"); + throw Exception("mrhistogram is not designed to handle images greater than 4D"); if (header.datatype().is_complex()) - throw Exception ("histogram generation not supported for complex data types"); + throw Exception("histogram generation not supported for complex data types"); auto data = header.get_image(); - const bool allvolumes = get_options ("allvolumes").size(); - size_t nbins = get_option_value ("bins", 0); + const bool allvolumes = get_options("allvolumes").size(); + size_t nbins = get_option_value("bins", 0); - auto opt = get_options ("mask"); + auto opt = get_options("mask"); Image mask; if (opt.size()) { - mask = Image::open (opt[0][0]); - check_dimensions (mask, header, 0, 3); + mask = Image::open(opt[0][0]); + check_dimensions(mask, header, 0, 3); } - File::OFStream output (argument[1]); + File::OFStream output(argument[1]); output << "# " << App::command_history_string << "\n"; - Algo::Histogram::Calibrator calibrator (nbins, get_options ("ignorezero").size()); - opt = get_options ("template"); + Algo::Histogram::Calibrator calibrator(nbins, get_options("ignorezero").size()); + opt = get_options("template"); if (opt.size()) { - calibrator.from_file (opt[0][0]); + calibrator.from_file(opt[0][0]); } else { for (auto v = Volume_loop(data); v; ++v) - run_volume (calibrator, data, mask); + run_volume(calibrator, data, mask); // If getting min/max using all volumes, but generating a single histogram per volume, // then want the automatic calculation of bin width to be based on the number of // voxels per volume, rather than the total number of values sent to the calibrator - calibrator.finalize (header.ndim() > 3 && !allvolumes ? header.size(3) : 1, - header.datatype().is_integer() && header.intensity_offset() == 0.0 && header.intensity_scale() == 1.0); + calibrator.finalize(header.ndim() > 3 && !allvolumes ? header.size(3) : 1, + header.datatype().is_integer() && header.intensity_offset() == 0.0 && + header.intensity_scale() == 1.0); } nbins = calibrator.get_num_bins(); if (!nbins) - throw Exception (std::string("No histogram bins constructed") + - ((get_options ("ignorezero").size() || get_options ("bins").size()) ? - "." : - "; you might want to use the -ignorezero or -bins option.")); + throw Exception(std::string("No histogram bins constructed") + + ((get_options("ignorezero").size() || get_options("bins").size()) + ? "." + : "; you might want to use the -ignorezero or -bins option.")); for (size_t i = 0; i != nbins; ++i) - output << (calibrator.get_min() + ((i+0.5) * calibrator.get_bin_width())) << ","; + output << (calibrator.get_min() + ((i + 0.5) * calibrator.get_bin_width())) << ","; output << "\n"; if (allvolumes) { - Algo::Histogram::Data histogram (calibrator); + Algo::Histogram::Data histogram(calibrator); for (auto v = Volume_loop(data); v; ++v) - run_volume (histogram, data, mask); + run_volume(histogram, data, mask); for (size_t i = 0; i != nbins; ++i) output << histogram[i] << ","; output << "\n"; @@ -154,13 +137,11 @@ void run () } else { for (auto v = Volume_loop(data); v; ++v) { - Algo::Histogram::Data histogram (calibrator); - run_volume (histogram, data, mask); + Algo::Histogram::Data histogram(calibrator); + run_volume(histogram, data, mask); for (size_t i = 0; i != nbins; ++i) output << histogram[i] << ","; output << "\n"; } - } } - diff --git a/cmd/mrinfo.cpp b/cmd/mrinfo.cpp index 7cb54e1526..2592043b86 100644 --- a/cmd/mrinfo.cpp +++ b/cmd/mrinfo.cpp @@ -18,143 +18,127 @@ #include #include "command.h" -#include "header.h" -#include "phase_encoding.h" -#include "types.h" +#include "dwi/gradient.h" #include "file/json.h" #include "file/json_utils.h" -#include "dwi/gradient.h" +#include "header.h" #include "image_io/pipe.h" - +#include "phase_encoding.h" +#include "types.h" using namespace MR; using namespace App; - - const OptionGroup GradImportOptions = DWI::GradImportOptions(); const OptionGroup GradExportOptions = DWI::GradExportOptions(); -const OptionGroup FieldExportOptions = OptionGroup ("Options for exporting image header fields") - - + Option ("property", "any text properties embedded in the image header under the " - "specified key (use 'all' to list all keys found)").allow_multiple() - + Argument ("key").type_text() +const OptionGroup FieldExportOptions = + OptionGroup("Options for exporting image header fields") - + Option ("json_keyval", "export header key/value entries to a JSON file") - + Argument ("file").type_file_out() + + Option("property", + "any text properties embedded in the image header under the " + "specified key (use 'all' to list all keys found)") + .allow_multiple() + + Argument("key").type_text() - + Option ("json_all", "export all header contents to a JSON file") - + Argument ("file").type_file_out(); + + Option("json_keyval", "export header key/value entries to a JSON file") + Argument("file").type_file_out() + + Option("json_all", "export all header contents to a JSON file") + Argument("file").type_file_out(); - -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (d.tournier@brain.org.au) and Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Display image header information, or extract specific information from the header"; DESCRIPTION - + "By default, all information contained in each image header will be printed to the console in " - "a reader-friendly format." + +"By default, all information contained in each image header will be printed to the console in " + "a reader-friendly format." - + "Alternatively, command-line options may be used to extract specific details from the header(s); " - "these are printed to the console in a format more appropriate for scripting purposes or " - "piping to file. If multiple options and/or images are provided, the requested header fields " - "will be printed in the order in which they appear in the help page, with all requested details " - "from each input image in sequence printed before the next image is processed." + + "Alternatively, command-line options may be used to extract specific details from the header(s); " + "these are printed to the console in a format more appropriate for scripting purposes or " + "piping to file. If multiple options and/or images are provided, the requested header fields " + "will be printed in the order in which they appear in the help page, with all requested details " + "from each input image in sequence printed before the next image is processed." - + "The command can also write the diffusion gradient table from a single input image to file; " - "either in the MRtrix or FSL format (bvecs/bvals file pair; includes appropriate diffusion " - "gradient vector reorientation)" + + "The command can also write the diffusion gradient table from a single input image to file; " + "either in the MRtrix or FSL format (bvecs/bvals file pair; includes appropriate diffusion " + "gradient vector reorientation)" - + "The -dwgrad, -export_* and -shell_* options provide (information about) " - "the diffusion weighting gradient table after it has been processed by " - "the MRtrix3 back-end (vectors normalised, b-values scaled by the square " - "of the vector norm, depending on the -bvalue_scaling option). To see the " - "raw gradient table information as stored in the image header, i.e. without " - "MRtrix3 back-end processing, use \"-property dw_scheme\"." + + "The -dwgrad, -export_* and -shell_* options provide (information about) " + "the diffusion weighting gradient table after it has been processed by " + "the MRtrix3 back-end (vectors normalised, b-values scaled by the square " + "of the vector norm, depending on the -bvalue_scaling option). To see the " + "raw gradient table information as stored in the image header, i.e. without " + "MRtrix3 back-end processing, use \"-property dw_scheme\"." - + DWI::bvalue_scaling_description; + + DWI::bvalue_scaling_description; ARGUMENTS - + Argument ("image", "the input image(s).").allow_multiple().type_image_in(); + +Argument("image", "the input image(s).").allow_multiple().type_image_in(); OPTIONS - + Option ("all", "print all properties, rather than the first and last 2 of each.") - + Option ("name", "print the file system path of the image") - + Option ("format", "image file format") - + Option ("ndim", "number of image dimensions") - + Option ("size", "image size along each axis") - + Option ("spacing", "voxel spacing along each image dimension") - + Option ("datatype", "data type used for image data storage") - + Option ("strides", "data strides i.e. order and direction of axes data layout") - + Option ("offset", "image intensity offset") - + Option ("multiplier", "image intensity multiplier") - + Option ("transform", "the transformation from image coordinates [mm] to scanner / real world coordinates [mm]") - - + FieldExportOptions - - + DWI::GradImportOptions() - + DWI::bvalue_scaling_option - - + GradExportOptions - + Option ("dwgrad", "the diffusion-weighting gradient table, as interpreted by MRtrix3") - + Option ("shell_bvalues", "list the average b-value of each shell") - + Option ("shell_sizes", "list the number of volumes in each shell") - + Option ("shell_indices", "list the image volumes attributed to each b-value shell") - - + PhaseEncoding::ExportOptions - + Option ("petable", "print the phase encoding table") - - + OptionGroup ("Handling of piped images") - + Option ("nodelete", "don't delete temporary images or images passed to mrinfo via Unix pipes"); - -} - + +Option("all", "print all properties, rather than the first and last 2 of each.") + + Option("name", "print the file system path of the image") + Option("format", "image file format") + + Option("ndim", "number of image dimensions") + Option("size", "image size along each axis") + + Option("spacing", "voxel spacing along each image dimension") + + Option("datatype", "data type used for image data storage") + + Option("strides", "data strides i.e. order and direction of axes data layout") + + Option("offset", "image intensity offset") + Option("multiplier", "image intensity multiplier") + + Option("transform", "the transformation from image coordinates [mm] to scanner / real world coordinates [mm]") + + FieldExportOptions + + DWI::GradImportOptions() + DWI::bvalue_scaling_option + + GradExportOptions + Option("dwgrad", "the diffusion-weighting gradient table, as interpreted by MRtrix3") + + Option("shell_bvalues", "list the average b-value of each shell") + + Option("shell_sizes", "list the number of volumes in each shell") + + Option("shell_indices", "list the image volumes attributed to each b-value shell") + + PhaseEncoding::ExportOptions + Option("petable", "print the phase encoding table") + + OptionGroup("Handling of piped images") + + Option("nodelete", "don't delete temporary images or images passed to mrinfo via Unix pipes"); +} -void print_dimensions (const Header& header) -{ +void print_dimensions(const Header &header) { std::string buffer; for (size_t i = 0; i < header.ndim(); ++i) { - if (i) buffer += " "; - buffer += str (header.size (i)); + if (i) + buffer += " "; + buffer += str(header.size(i)); } std::cout << buffer << "\n"; } -void print_spacing (const Header& header) -{ +void print_spacing(const Header &header) { std::string buffer; for (size_t i = 0; i < header.ndim(); ++i) { - if (i) buffer += " "; - buffer += str (header.spacing (i)); + if (i) + buffer += " "; + buffer += str(header.spacing(i)); } std::cout << buffer << "\n"; } -void print_strides (const Header& header) -{ +void print_strides(const Header &header) { std::string buffer; - vector strides (Stride::get (header)); - Stride::symbolise (strides); + vector strides(Stride::get(header)); + Stride::symbolise(strides); for (size_t i = 0; i < header.ndim(); ++i) { - if (i) buffer += " "; - buffer += header.stride (i) ? str (strides[i]) : "?"; + if (i) + buffer += " "; + buffer += header.stride(i) ? str(strides[i]) : "?"; } std::cout << buffer << "\n"; } -void print_shells (const Eigen::MatrixXd& grad, const bool shell_bvalues, const bool shell_sizes, const bool shell_indices) -{ - DWI::Shells dwshells (grad); +void print_shells(const Eigen::MatrixXd &grad, + const bool shell_bvalues, + const bool shell_sizes, + const bool shell_indices) { + DWI::Shells dwshells(grad); if (shell_bvalues) { for (size_t i = 0; i < dwshells.count(); i++) std::cout << dwshells[i].get_mean() << " "; @@ -172,77 +156,67 @@ void print_shells (const Eigen::MatrixXd& grad, const bool shell_bvalues, const } } -void print_transform (const Header& header) -{ - Eigen::IOFormat fmt (Eigen::FullPrecision, 0, " ", "\n", "", "", "", "\n"); +void print_transform(const Header &header) { + Eigen::IOFormat fmt(Eigen::FullPrecision, 0, " ", "\n", "", "", "", "\n"); Eigen::Matrix matrix; - matrix.topLeftCorner<3,4>() = header.transform().matrix(); + matrix.topLeftCorner<3, 4>() = header.transform().matrix(); matrix.row(3) << 0.0, 0.0, 0.0, 1.0; - std::cout << matrix.format (fmt); + std::cout << matrix.format(fmt); } -void print_properties (const Header& header, const std::string& key, const size_t indent = 0) -{ - if (lowercase (key) == "all") { - for (const auto& it : header.keyval()) { +void print_properties(const Header &header, const std::string &key, const size_t indent = 0) { + if (lowercase(key) == "all") { + for (const auto &it : header.keyval()) { std::cout << it.first << ": "; - print_properties (header, it.first, it.first.size()+2); + print_properties(header, it.first, it.first.size() + 2); } - } - else { - const auto values = header.keyval().find (key); + } else { + const auto values = header.keyval().find(key); if (values != header.keyval().end()) { - auto lines = split (values->second, "\n"); - INFO ("showing property " + key + ":"); + auto lines = split(values->second, "\n"); + INFO("showing property " + key + ":"); std::cout << lines[0] << "\n"; for (size_t i = 1; i != lines.size(); ++i) { - lines[i].insert (0, indent, ' '); + lines[i].insert(0, indent, ' '); std::cout << lines[i] << "\n"; } } else { - WARN ("no \"" + key + "\" entries found in \"" + header.name() + "\""); + WARN("no \"" + key + "\" entries found in \"" + header.name() + "\""); } } } -void header2json (const Header& header, nlohmann::json& json) -{ +void header2json(const Header &header, nlohmann::json &json) { // Capture _all_ header fields, not just the optional key-value pairs json["name"] = header.name(); - vector size (header.ndim()); - vector spacing (header.ndim()); + vector size(header.ndim()); + vector spacing(header.ndim()); for (size_t axis = 0; axis != header.ndim(); ++axis) { - size[axis] = header.size (axis); - spacing[axis] = header.spacing (axis); + size[axis] = header.size(axis); + spacing[axis] = header.spacing(axis); } json["size"] = size; json["spacing"] = spacing; - vector strides (Stride::get (header)); - Stride::symbolise (strides); + vector strides(Stride::get(header)); + Stride::symbolise(strides); json["strides"] = strides; json["format"] = header.format(); json["datatype"] = header.datatype().specifier(); json["intensity_offset"] = header.intensity_offset(); json["intensity_scale"] = header.intensity_scale(); - const transform_type& T (header.transform()); - json["transform"] = { { T(0,0), T(0,1), T(0,2), T(0,3) }, - { T(1,0), T(1,1), T(1,2), T(1,3) }, - { T(2,0), T(2,1), T(2,2), T(2,3) }, - { 0.0, 0.0, 0.0, 1.0 } }; + const transform_type &T(header.transform()); + json["transform"] = {{T(0, 0), T(0, 1), T(0, 2), T(0, 3)}, + {T(1, 0), T(1, 1), T(1, 2), T(1, 3)}, + {T(2, 0), T(2, 1), T(2, 2), T(2, 3)}, + {0.0, 0.0, 0.0, 1.0}}; // Load key-value entries into a nested keyval.* member - File::JSON::write (header, json["keyval"], header.name()); + File::JSON::write(header, json["keyval"], header.name()); } - - - - - -void run () -{ - auto check_option_group = [](const App::OptionGroup& g) { +void run() { + auto check_option_group = [](const App::OptionGroup &g) { for (auto o : g) - if (get_options (o.id).size()) + if (get_options(o.id).size()) return true; return false; }; @@ -250,98 +224,107 @@ void run () if (get_options("nodelete").size()) ImageIO::Pipe::delete_piped_images = false; - const bool export_grad = check_option_group (GradExportOptions); - const bool export_pe = check_option_group (PhaseEncoding::ExportOptions); + const bool export_grad = check_option_group(GradExportOptions); + const bool export_pe = check_option_group(PhaseEncoding::ExportOptions); if (export_grad && argument.size() > 1) - throw Exception ("can only export DW gradient table to file if a single input image is provided"); + throw Exception("can only export DW gradient table to file if a single input image is provided"); if (export_pe && argument.size() > 1) - throw Exception ("can only export phase encoding table to file if a single input image is provided"); + throw Exception("can only export phase encoding table to file if a single input image is provided"); - std::unique_ptr json_keyval (get_options ("json_keyval").size() ? new nlohmann::json : nullptr); - std::unique_ptr json_all (get_options ("json_all").size() ? new nlohmann::json : nullptr); + std::unique_ptr json_keyval(get_options("json_keyval").size() ? new nlohmann::json : nullptr); + std::unique_ptr json_all(get_options("json_all").size() ? new nlohmann::json : nullptr); if (json_all && argument.size() > 1) - throw Exception ("Cannot use -json_all option with multiple input images"); - - const bool name = get_options("name") .size(); - const bool format = get_options("format") .size(); - const bool ndim = get_options("ndim") .size(); - const bool size = get_options("size") .size(); - const bool spacing = get_options("spacing") .size(); - const bool datatype = get_options("datatype") .size(); - const bool strides = get_options("strides") .size(); - const bool offset = get_options("offset") .size(); - const bool multiplier = get_options("multiplier") .size(); - const auto properties = get_options("property"); - const bool transform = get_options("transform") .size(); - const bool dwgrad = get_options("dwgrad") .size(); - const bool shell_bvalues = get_options("shell_bvalues") .size(); - const bool shell_sizes = get_options("shell_sizes") .size(); - const bool shell_indices = get_options("shell_indices") .size(); - const bool petable = get_options("petable") .size(); - - const bool print_full_header = !(format || ndim || size || spacing || datatype || strides || - offset || multiplier || properties.size() || transform || - dwgrad || export_grad || shell_bvalues || shell_sizes || shell_indices || - export_pe || petable || - json_keyval || json_all); + throw Exception("Cannot use -json_all option with multiple input images"); + + const bool name = get_options("name").size(); + const bool format = get_options("format").size(); + const bool ndim = get_options("ndim").size(); + const bool size = get_options("size").size(); + const bool spacing = get_options("spacing").size(); + const bool datatype = get_options("datatype").size(); + const bool strides = get_options("strides").size(); + const bool offset = get_options("offset").size(); + const bool multiplier = get_options("multiplier").size(); + const auto properties = get_options("property"); + const bool transform = get_options("transform").size(); + const bool dwgrad = get_options("dwgrad").size(); + const bool shell_bvalues = get_options("shell_bvalues").size(); + const bool shell_sizes = get_options("shell_sizes").size(); + const bool shell_indices = get_options("shell_indices").size(); + const bool petable = get_options("petable").size(); + + const bool print_full_header = !(format || ndim || size || spacing || datatype || strides || offset || multiplier || + properties.size() || transform || dwgrad || export_grad || shell_bvalues || + shell_sizes || shell_indices || export_pe || petable || json_keyval || json_all); for (size_t i = 0; i < argument.size(); ++i) { - const auto header = Header::open (argument[i]); - - if (name) std::cout << header.name() << "\n"; - if (format) std::cout << header.format() << "\n"; - if (ndim) std::cout << header.ndim() << "\n"; - if (size) print_dimensions (header); - if (spacing) print_spacing (header); - if (datatype) std::cout << (header.datatype().specifier() ? header.datatype().specifier() : "invalid") << "\n"; - if (strides) print_strides (header); - if (offset) std::cout << header.intensity_offset() << "\n"; - if (multiplier) std::cout << header.intensity_scale() << "\n"; - if (transform) print_transform (header); - if (petable) std::cout << PhaseEncoding::get_scheme (header) << "\n"; + const auto header = Header::open(argument[i]); + + if (name) + std::cout << header.name() << "\n"; + if (format) + std::cout << header.format() << "\n"; + if (ndim) + std::cout << header.ndim() << "\n"; + if (size) + print_dimensions(header); + if (spacing) + print_spacing(header); + if (datatype) + std::cout << (header.datatype().specifier() ? header.datatype().specifier() : "invalid") << "\n"; + if (strides) + print_strides(header); + if (offset) + std::cout << header.intensity_offset() << "\n"; + if (multiplier) + std::cout << header.intensity_scale() << "\n"; + if (transform) + print_transform(header); + if (petable) + std::cout << PhaseEncoding::get_scheme(header) << "\n"; for (size_t n = 0; n < properties.size(); ++n) - print_properties (header, properties[n][0]); + print_properties(header, properties[n][0]); Eigen::MatrixXd grad; - if (export_grad || check_option_group (GradImportOptions) || dwgrad || - shell_bvalues || shell_sizes || shell_indices) { - grad = DWI::get_DW_scheme (header, DWI::get_cmdline_bvalue_scaling_behaviour()); + if (export_grad || check_option_group(GradImportOptions) || dwgrad || shell_bvalues || shell_sizes || + shell_indices) { + grad = DWI::get_DW_scheme(header, DWI::get_cmdline_bvalue_scaling_behaviour()); if (dwgrad) { - Eigen::IOFormat fmt (Eigen::FullPrecision, 0, " ", "\n", "", "", "", ""); + Eigen::IOFormat fmt(Eigen::FullPrecision, 0, " ", "\n", "", "", "", ""); std::cout << grad.format(fmt) << "\n"; } if (shell_bvalues || shell_sizes || shell_indices) - print_shells (grad, shell_bvalues, shell_sizes, shell_indices); + print_shells(grad, shell_bvalues, shell_sizes, shell_indices); } - DWI::export_grad_commandline (header); - PhaseEncoding::export_commandline (header); + DWI::export_grad_commandline(header); + PhaseEncoding::export_commandline(header); if (json_keyval) - File::JSON::write (header, *json_keyval, (argument.size() > 1 ? std::string("") : std::string(argument[0]))); + File::JSON::write(header, *json_keyval, (argument.size() > 1 ? std::string("") : std::string(argument[0]))); if (json_all) - header2json (header, *json_all); + header2json(header, *json_all); if (print_full_header) - std::cout << header.description (get_options ("all").size()); + std::cout << header.description(get_options("all").size()); } if (json_keyval) { - auto opt = get_options ("json_keyval"); - assert (opt.size()); - File::OFStream out (opt[0][0]); + auto opt = get_options("json_keyval"); + assert(opt.size()); + File::OFStream out(opt[0][0]); out << json_keyval->dump(4) << "\n"; } if (json_all) { - auto opt = get_options ("json_all"); - assert (opt.size()); - File::OFStream out (opt[0][0]); + auto opt = get_options("json_all"); + assert(opt.size()); + File::OFStream out(opt[0][0]); out << json_all->dump(4) << "\n"; } } diff --git a/cmd/mrmath.cpp b/cmd/mrmath.cpp index 9dd0e27d45..ee7ac7decd 100644 --- a/cmd/mrmath.cpp +++ b/cmd/mrmath.cpp @@ -16,414 +16,400 @@ #include +#include "algo/threaded_loop.h" #include "command.h" +#include "dwi/gradient.h" #include "image.h" +#include "math/math.h" +#include "math/median.h" #include "memory.h" #include "phase_encoding.h" #include "progressbar.h" -#include "algo/threaded_loop.h" -#include "math/math.h" -#include "math/median.h" -#include "dwi/gradient.h" #include - using namespace MR; using namespace App; -const char* operations[] = { - "mean", - "median", - "sum", - "product", - "rms", - "norm", - "var", - "std", - "min", - "max", - "absmax", // Maximum of absolute values - "magmax", // Value for which the magnitude is the maximum (i.e. preserves signed-ness) - NULL -}; - -void usage () -{ +const char *operations[] = {"mean", + "median", + "sum", + "product", + "rms", + "norm", + "var", + "std", + "min", + "max", + "absmax", // Maximum of absolute values + "magmax", // Value for which the magnitude is the maximum (i.e. preserves signed-ness) + NULL}; + +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Compute summary statistic on image intensities either across images, " "or along a specified axis of a single image"; DESCRIPTION - + "Supported operations are:" + +"Supported operations are:" - + "mean, median, sum, product, rms (root-mean-square value), norm (vector 2-norm), var (unbiased variance), " - "std (unbiased standard deviation), min, max, absmax (maximum absolute value), " - "magmax (value with maximum absolute value, preserving its sign)." + + "mean, median, sum, product, rms (root-mean-square value), norm (vector 2-norm), var (unbiased variance), " + "std (unbiased standard deviation), min, max, absmax (maximum absolute value), " + "magmax (value with maximum absolute value, preserving its sign)." - + "This command is used to traverse either along an image axis, or across a " - "set of input images, calculating some statistic from the values along each " - "traversal. If you are seeking to instead perform mathematical calculations " - "that are done independently for each voxel, pleaase see the 'mrcalc' command."; + + "This command is used to traverse either along an image axis, or across a " + "set of input images, calculating some statistic from the values along each " + "traversal. If you are seeking to instead perform mathematical calculations " + "that are done independently for each voxel, pleaase see the 'mrcalc' command."; EXAMPLES - + Example ("Calculate a 3D volume representing the mean intensity across a 4D image series", - "mrmath 4D.mif mean 3D_mean.mif -axis 3", - "This is a common operation for calculating e.g. the mean value within a " - "specific DWI b-value. Note that axis indices start from 0; thus, axes 0, 1 & 2 " - "are the three spatial axes, and axis 3 operates across volumes.") + +Example("Calculate a 3D volume representing the mean intensity across a 4D image series", + "mrmath 4D.mif mean 3D_mean.mif -axis 3", + "This is a common operation for calculating e.g. the mean value within a " + "specific DWI b-value. Note that axis indices start from 0; thus, axes 0, 1 & 2 " + "are the three spatial axes, and axis 3 operates across volumes.") - + Example ("Generate a Maximum Intensity Projection (MIP) along the inferior-superior direction", - "mrmath input.mif max MIP.mif -axis 2", - "Since a MIP is literally the maximal value along a specific projection direction, " - "axis-aligned MIPs can be generated easily using mrmath with the \'max\' operation."); + + Example("Generate a Maximum Intensity Projection (MIP) along the inferior-superior direction", + "mrmath input.mif max MIP.mif -axis 2", + "Since a MIP is literally the maximal value along a specific projection direction, " + "axis-aligned MIPs can be generated easily using mrmath with the \'max\' operation."); ARGUMENTS - + Argument ("input", "the input image(s).").type_image_in ().allow_multiple() - + Argument ("operation", "the operation to apply, one of: " + join(operations, ", ") + ".").type_choice (operations) - + Argument ("output", "the output image.").type_image_out (); + +Argument("input", "the input image(s).").type_image_in().allow_multiple() + + Argument("operation", "the operation to apply, one of: " + join(operations, ", ") + ".").type_choice(operations) + + Argument("output", "the output image.").type_image_out(); OPTIONS - + Option ("axis", "perform operation along a specified axis of a single input image") - + Argument ("index").type_integer (0) + +Option("axis", "perform operation along a specified axis of a single input image") + + Argument("index").type_integer(0) - + Option ("keep_unary_axes", "Keep unary axes in input images prior to calculating the stats. " - "The default is to wipe axes with single elements.") + + Option("keep_unary_axes", + "Keep unary axes in input images prior to calculating the stats. " + "The default is to wipe axes with single elements.") - + DataType::options(); + + DataType::options(); } - using value_type = float; - -class Mean { - public: - Mean () : sum (0.0), count (0) { } - void operator() (value_type val) { - if (std::isfinite (val)) { - sum += val; - ++count; - } +class Mean { +public: + Mean() : sum(0.0), count(0) {} + void operator()(value_type val) { + if (std::isfinite(val)) { + sum += val; + ++count; } - value_type result () const { - if (!count) - return NAN; - return sum / count; - } - double sum; - size_t count; + } + value_type result() const { + if (!count) + return NAN; + return sum / count; + } + double sum; + size_t count; }; -class Median { - public: - Median () { } - void operator() (value_type val) { - if (!std::isnan (val)) - values.push_back(val); - } - value_type result () { - return Math::median(values); - } - vector values; +class Median { +public: + Median() {} + void operator()(value_type val) { + if (!std::isnan(val)) + values.push_back(val); + } + value_type result() { return Math::median(values); } + vector values; }; -class Sum { - public: - Sum () : sum (0.0) { } - void operator() (value_type val) { - if (std::isfinite (val)) - sum += val; - } - value_type result () const { - return sum; - } - double sum; +class Sum { +public: + Sum() : sum(0.0) {} + void operator()(value_type val) { + if (std::isfinite(val)) + sum += val; + } + value_type result() const { return sum; } + double sum; }; - -class Product { - public: - Product () : product (NAN) { } - void operator() (value_type val) { - if (std::isfinite (val)) - product = std::isfinite (product) ? product * val : val; - } - value_type result () const { - return product; - } - double product; +class Product { +public: + Product() : product(NAN) {} + void operator()(value_type val) { + if (std::isfinite(val)) + product = std::isfinite(product) ? product * val : val; + } + value_type result() const { return product; } + double product; }; - -class RMS { - public: - RMS() : sum (0.0), count (0) { } - void operator() (value_type val) { - if (std::isfinite (val)) { - sum += Math::pow2 (val); - ++count; - } - } - value_type result() const { - if (!count) - return NAN; - return std::sqrt(sum / count); +class RMS { +public: + RMS() : sum(0.0), count(0) {} + void operator()(value_type val) { + if (std::isfinite(val)) { + sum += Math::pow2(val); + ++count; } - double sum; - size_t count; + } + value_type result() const { + if (!count) + return NAN; + return std::sqrt(sum / count); + } + double sum; + size_t count; }; -class NORM2 { - public: - NORM2() : sum (0.0), count (0) { } - void operator() (value_type val) { - if (std::isfinite (val)) { - sum += Math::pow2 (val); - ++count; - } - } - value_type result() const { - if (!count) - return NAN; - return std::sqrt(sum); +class NORM2 { +public: + NORM2() : sum(0.0), count(0) {} + void operator()(value_type val) { + if (std::isfinite(val)) { + sum += Math::pow2(val); + ++count; } - double sum; - size_t count; + } + value_type result() const { + if (!count) + return NAN; + return std::sqrt(sum); + } + double sum; + size_t count; }; - // Welford's algorithm to avoid catastrophic cancellation -class Var { - public: - Var () : delta (0.0), delta2 (0.0), mean (0.0), m2 (0.0), count (0) { } - void operator() (value_type val) { - if (std::isfinite (val)) { - ++count; - delta = val - mean; - mean += delta / count; - delta2 = val - mean; - m2 += delta * delta2; - } - } - value_type result () const { - if (count < 2) - return NAN; - return m2 / (static_cast (count) - 1.0); +class Var { +public: + Var() : delta(0.0), delta2(0.0), mean(0.0), m2(0.0), count(0) {} + void operator()(value_type val) { + if (std::isfinite(val)) { + ++count; + delta = val - mean; + mean += delta / count; + delta2 = val - mean; + m2 += delta * delta2; } - double delta, delta2, mean, m2; - size_t count; + } + value_type result() const { + if (count < 2) + return NAN; + return m2 / (static_cast(count) - 1.0); + } + double delta, delta2, mean, m2; + size_t count; }; - -class Std : public Var { - public: - Std() : Var() { } - value_type result () const { return std::sqrt (Var::result()); } +class Std : public Var { +public: + Std() : Var() {} + value_type result() const { return std::sqrt(Var::result()); } }; - -class Min { - public: - Min () : min (std::numeric_limits::infinity()) { } - void operator() (value_type val) { - if (std::isfinite (val) && val < min) - min = val; - } - value_type result () const { return std::isfinite (min) ? min : NAN; } - value_type min; +class Min { +public: + Min() : min(std::numeric_limits::infinity()) {} + void operator()(value_type val) { + if (std::isfinite(val) && val < min) + min = val; + } + value_type result() const { return std::isfinite(min) ? min : NAN; } + value_type min; }; - -class Max { - public: - Max () : max (-std::numeric_limits::infinity()) { } - void operator() (value_type val) { - if (std::isfinite (val) && val > max) - max = val; - } - value_type result () const { return std::isfinite (max) ? max : NAN; } - value_type max; +class Max { +public: + Max() : max(-std::numeric_limits::infinity()) {} + void operator()(value_type val) { + if (std::isfinite(val) && val > max) + max = val; + } + value_type result() const { return std::isfinite(max) ? max : NAN; } + value_type max; }; - -class AbsMax { - public: - AbsMax () : max (-std::numeric_limits::infinity()) { } - void operator() (value_type val) { - if (std::isfinite (val) && abs(val) > max) - max = abs(val); - } - value_type result () const { return std::isfinite (max) ? max : NAN; } - value_type max; +class AbsMax { +public: + AbsMax() : max(-std::numeric_limits::infinity()) {} + void operator()(value_type val) { + if (std::isfinite(val) && abs(val) > max) + max = abs(val); + } + value_type result() const { return std::isfinite(max) ? max : NAN; } + value_type max; }; -class MagMax { - public: - MagMax () : max (-std::numeric_limits::infinity()) { } - MagMax (const int i) : max (-std::numeric_limits::infinity()) { } - void operator() (value_type val) { - if (std::isfinite (val) && (!std::isfinite (max) || abs(val) > abs (max))) - max = val; - } - value_type result () const { return std::isfinite (max) ? max : NAN; } - value_type max; +class MagMax { +public: + MagMax() : max(-std::numeric_limits::infinity()) {} + MagMax(const int i) : max(-std::numeric_limits::infinity()) {} + void operator()(value_type val) { + if (std::isfinite(val) && (!std::isfinite(max) || abs(val) > abs(max))) + max = val; + } + value_type result() const { return std::isfinite(max) ? max : NAN; } + value_type max; }; +template class AxisKernel { +public: + AxisKernel(size_t axis) : axis(axis) {} + template void operator()(InputImageType &in, OutputImageType &out) { + Operation op; + for (auto l = Loop(axis)(in); l; ++l) + op(in.value()); + out.value() = op.result(); + } - - -template -class AxisKernel { - public: - AxisKernel (size_t axis) : axis (axis) { } - - template - void operator() (InputImageType& in, OutputImageType& out) { - Operation op; - for (auto l = Loop (axis) (in); l; ++l) - op (in.value()); - out.value() = op.result(); - } - protected: - const size_t axis; +protected: + const size_t axis; }; - - - - -class ImageKernelBase { - public: - virtual ~ImageKernelBase () { } - virtual void process (Header& image_in) = 0; - virtual void write_back (Image& out) = 0; +class ImageKernelBase { +public: + virtual ~ImageKernelBase() {} + virtual void process(Header &image_in) = 0; + virtual void write_back(Image &out) = 0; }; - - -template -class ImageKernel : public ImageKernelBase { - protected: - class InitFunctor { - public: - template - void operator() (ImageType& out) const { out.value() = Operation(); } - }; - class ProcessFunctor { - public: - template - void operator() (ImageType1& out, ImageType2& in) const { - Operation op = out.value(); - op (in.value()); - out.value() = op; - } - }; - class ResultFunctor { - public: - template - void operator() (ImageType1& out, ImageType2& in) const { - Operation op = in.value(); - out.value() = op.result(); - } - }; - +template class ImageKernel : public ImageKernelBase { +protected: + class InitFunctor { public: - ImageKernel (const Header& header) : - image (Header::scratch (header).get_image()) { - ThreadedLoop (image).run (InitFunctor(), image); - } - - void write_back (Image& out) - { - ThreadedLoop (image).run (ResultFunctor(), out, image); + template void operator()(ImageType &out) const { out.value() = Operation(); } + }; + class ProcessFunctor { + public: + template void operator()(ImageType1 &out, ImageType2 &in) const { + Operation op = out.value(); + op(in.value()); + out.value() = op; } - - void process (Header& header_in) - { - auto in = header_in.get_image(); - ThreadedLoop (image).run (ProcessFunctor(), image, in); + }; + class ResultFunctor { + public: + template void operator()(ImageType1 &out, ImageType2 &in) const { + Operation op = in.value(); + out.value() = op.result(); } + }; - protected: - Image image; -}; +public: + ImageKernel(const Header &header) : image(Header::scratch(header).get_image()) { + ThreadedLoop(image).run(InitFunctor(), image); + } + void write_back(Image &out) { ThreadedLoop(image).run(ResultFunctor(), out, image); } + void process(Header &header_in) { + auto in = header_in.get_image(); + ThreadedLoop(image).run(ProcessFunctor(), image, in); + } +protected: + Image image; +}; -void run () -{ +void run() { const size_t num_inputs = argument.size() - 2; const int op = argument[num_inputs]; - const std::string& output_path = argument.back(); + const std::string &output_path = argument.back(); - auto opt = get_options ("axis"); + auto opt = get_options("axis"); if (opt.size()) { if (num_inputs != 1) - throw Exception ("Option -axis only applies if a single input image is used"); + throw Exception("Option -axis only applies if a single input image is used"); const size_t axis = opt[0][0]; - auto image_in = Header::open (argument[0]).get_image().with_direct_io (axis); + auto image_in = Header::open(argument[0]).get_image().with_direct_io(axis); if (axis >= image_in.ndim()) - throw Exception ("Cannot perform operation along axis " + str (axis) + "; image only has " + str(image_in.ndim()) + " axes"); + throw Exception("Cannot perform operation along axis " + str(axis) + "; image only has " + str(image_in.ndim()) + + " axes"); - Header header_out (image_in); + Header header_out(image_in); if (axis == 3) { try { - const auto DW_scheme = DWI::parse_DW_scheme (header_out); - DWI::stash_DW_scheme (header_out, DW_scheme); - } catch (...) { } - DWI::clear_DW_scheme (header_out); - PhaseEncoding::clear_scheme (header_out); + const auto DW_scheme = DWI::parse_DW_scheme(header_out); + DWI::stash_DW_scheme(header_out, DW_scheme); + } catch (...) { + } + DWI::clear_DW_scheme(header_out); + PhaseEncoding::clear_scheme(header_out); } - header_out.datatype() = DataType::from_command_line (DataType::Float32); + header_out.datatype() = DataType::from_command_line(DataType::Float32); header_out.size(axis) = 1; - squeeze_dim (header_out); + squeeze_dim(header_out); - auto image_out = Header::create (output_path, header_out).get_image(); + auto image_out = Header::create(output_path, header_out).get_image(); - auto loop = ThreadedLoop (std::string("computing ") + operations[op] + " along axis " + str(axis) + "...", image_out); + auto loop = + ThreadedLoop(std::string("computing ") + operations[op] + " along axis " + str(axis) + "...", image_out); switch (op) { - case 0: loop.run (AxisKernel (axis), image_in, image_out); return; - case 1: loop.run (AxisKernel (axis), image_in, image_out); return; - case 2: loop.run (AxisKernel (axis), image_in, image_out); return; - case 3: loop.run (AxisKernel(axis), image_in, image_out); return; - case 4: loop.run (AxisKernel (axis), image_in, image_out); return; - case 5: loop.run (AxisKernel (axis), image_in, image_out); return; - case 6: loop.run (AxisKernel (axis), image_in, image_out); return; - case 7: loop.run (AxisKernel (axis), image_in, image_out); return; - case 8: loop.run (AxisKernel (axis), image_in, image_out); return; - case 9: loop.run (AxisKernel (axis), image_in, image_out); return; - case 10: loop.run (AxisKernel (axis), image_in, image_out); return; - case 11: loop.run (AxisKernel (axis), image_in, image_out); return; - default: assert (0); + case 0: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 1: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 2: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 3: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 4: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 5: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 6: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 7: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 8: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 9: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 10: + loop.run(AxisKernel(axis), image_in, image_out); + return; + case 11: + loop.run(AxisKernel(axis), image_in, image_out); + return; + default: + assert(0); } } else { if (num_inputs < 2) - throw Exception ("mrmath requires either multiple input images, or the -axis option to be provided"); + throw Exception("mrmath requires either multiple input images, or the -axis option to be provided"); // Pre-load all image headers - vector
headers_in (num_inputs); + vector
headers_in(num_inputs); // Header of first input image is the template to which all other input images are compared - headers_in[0] = Header::open (argument[0]); - Header header (headers_in[0]); - header.datatype() = DataType::from_command_line (DataType::Float32); + headers_in[0] = Header::open(argument[0]); + Header header(headers_in[0]); + header.datatype() = DataType::from_command_line(DataType::Float32); // Wipe any excess unary-dimensional axes - if ( ! get_options ("keep_unary_axes").size() ) { - while (header.size (header.ndim() - 1) == 1) + if (!get_options("keep_unary_axes").size()) { + while (header.size(header.ndim() - 1) == 1) header.ndim() = header.ndim() - 1; } @@ -431,54 +417,78 @@ void run () for (size_t i = 1; i != num_inputs; ++i) { const std::string path = argument[i]; // headers_in.push_back (std::unique_ptr
(new Header (Header::open (path)))); - headers_in[i] = Header::open (path); - const Header& temp (headers_in[i]); + headers_in[i] = Header::open(path); + const Header &temp(headers_in[i]); if (temp.ndim() < header.ndim()) - throw Exception ("Image " + path + " has fewer axes than first imput image " + header.name()); + throw Exception("Image " + path + " has fewer axes than first imput image " + header.name()); for (size_t axis = 0; axis != header.ndim(); ++axis) { if (temp.size(axis) != header.size(axis)) - throw Exception ("Dimensions of image " + path + " do not match those of first input image " + header.name()); + throw Exception("Dimensions of image " + path + " do not match those of first input image " + header.name()); } for (size_t axis = header.ndim(); axis != temp.ndim(); ++axis) { if (temp.size(axis) != 1) - throw Exception ("Image " + path + " has axis with non-unary dimension beyond first input image " + header.name()); + throw Exception("Image " + path + " has axis with non-unary dimension beyond first input image " + + header.name()); } - header.merge_keyval (temp); + header.merge_keyval(temp); } // Instantiate a kernel depending on the operation requested std::unique_ptr kernel; switch (op) { - case 0: kernel.reset (new ImageKernel (header)); break; - case 1: kernel.reset (new ImageKernel (header)); break; - case 2: kernel.reset (new ImageKernel (header)); break; - case 3: kernel.reset (new ImageKernel (header)); break; - case 4: kernel.reset (new ImageKernel (header)); break; - case 5: kernel.reset (new ImageKernel (header)); break; - case 6: kernel.reset (new ImageKernel (header)); break; - case 7: kernel.reset (new ImageKernel (header)); break; - case 8: kernel.reset (new ImageKernel (header)); break; - case 9: kernel.reset (new ImageKernel (header)); break; - case 10: kernel.reset (new ImageKernel (header)); break; - case 11: kernel.reset (new ImageKernel (header)); break; - default: assert (0); + case 0: + kernel.reset(new ImageKernel(header)); + break; + case 1: + kernel.reset(new ImageKernel(header)); + break; + case 2: + kernel.reset(new ImageKernel(header)); + break; + case 3: + kernel.reset(new ImageKernel(header)); + break; + case 4: + kernel.reset(new ImageKernel(header)); + break; + case 5: + kernel.reset(new ImageKernel(header)); + break; + case 6: + kernel.reset(new ImageKernel(header)); + break; + case 7: + kernel.reset(new ImageKernel(header)); + break; + case 8: + kernel.reset(new ImageKernel(header)); + break; + case 9: + kernel.reset(new ImageKernel(header)); + break; + case 10: + kernel.reset(new ImageKernel(header)); + break; + case 11: + kernel.reset(new ImageKernel(header)); + break; + default: + assert(0); } // Feed the input images to the kernel one at a time { - ProgressBar progress (std::string("computing ") + operations[op] + " across " - + str(headers_in.size()) + " images", num_inputs); + ProgressBar progress(std::string("computing ") + operations[op] + " across " + str(headers_in.size()) + " images", + num_inputs); for (size_t i = 0; i != headers_in.size(); ++i) { - assert (headers_in[i].valid()); - assert (headers_in[i].is_file_backed()); - kernel->process (headers_in[i]); + assert(headers_in[i].valid()); + assert(headers_in[i].is_file_backed()); + kernel->process(headers_in[i]); ++progress; } } - auto out = Header::create (output_path, header).get_image(); - kernel->write_back (out); + auto out = Header::create(output_path, header).get_image(); + kernel->write_back(out); } - } - diff --git a/cmd/mrmetric.cpp b/cmd/mrmetric.cpp index 5f714836f8..28b95c1ac5 100644 --- a/cmd/mrmetric.cpp +++ b/cmd/mrmetric.cpp @@ -14,320 +14,323 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "image.h" #include "algo/loop.h" #include "algo/threaded_loop.h" +#include "command.h" +#include "image.h" #include "math/math.h" -#include "math/average_space.h" +#include "filter/reslice.h" +#include "interp/cubic.h" #include "interp/linear.h" #include "interp/nearest.h" -#include "interp/cubic.h" #include "interp/sinc.h" -#include "filter/reslice.h" +#include "math/average_space.h" -#include "transform.h" -#include "registration/transform/rigid.h" #include "registration/metric/cross_correlation.h" #include "registration/metric/mean_squared.h" #include "registration/metric/params.h" #include "registration/metric/thread_kernel.h" +#include "registration/transform/rigid.h" +#include "transform.h" using namespace MR; using namespace App; -const char* interp_choices[] = { "nearest", "linear", "cubic", "sinc", NULL }; -const char* space_choices[] = { "voxel", "image1", "image2", "average", NULL }; +const char *interp_choices[] = {"nearest", "linear", "cubic", "sinc", NULL}; +const char *space_choices[] = {"voxel", "image1", "image2", "average", NULL}; template -inline void meansquared (const ValueType& value1, - const ValueType& value2, - Eigen::Matrix& cost){ - cost.array() += Math::pow2 (value1 - value2); +inline void +meansquared(const ValueType &value1, const ValueType &value2, Eigen::Matrix &cost) { + cost.array() += Math::pow2(value1 - value2); } template -inline void meansquared (const Eigen::Matrix& value1, - const Eigen::Matrix& value2, - Eigen::Matrix& cost) { +inline void meansquared(const Eigen::Matrix &value1, + const Eigen::Matrix &value2, + Eigen::Matrix &cost) { cost.array() += (value1 - value2).array().square(); } template -void reslice(size_t interp, ImageType1& input, ImageType2& output, const TransformType& trafo = Adapter::NoTransform, const OversampleType& oversample = Adapter::AutoOverSample, const ValueType out_of_bounds_value = 0.f){ - switch(interp){ - case 0: - Filter::reslice (input, output, trafo, Adapter::AutoOverSample, out_of_bounds_value); - DEBUG("Nearest"); - break; - case 1: - Filter::reslice (input, output, trafo, Adapter::AutoOverSample, out_of_bounds_value); - DEBUG("Linear"); - break; - case 2: - Filter::reslice (input, output, trafo, Adapter::AutoOverSample, out_of_bounds_value); - DEBUG("Cubic"); - break; - case 3: - Filter::reslice (input, output, trafo, Adapter::AutoOverSample, out_of_bounds_value); - DEBUG("Sinc"); - break; - default: - throw Exception ("Fixme: interpolation value invalid"); +void reslice(size_t interp, + ImageType1 &input, + ImageType2 &output, + const TransformType &trafo = Adapter::NoTransform, + const OversampleType &oversample = Adapter::AutoOverSample, + const ValueType out_of_bounds_value = 0.f) { + switch (interp) { + case 0: + Filter::reslice(input, output, trafo, Adapter::AutoOverSample, out_of_bounds_value); + DEBUG("Nearest"); + break; + case 1: + Filter::reslice(input, output, trafo, Adapter::AutoOverSample, out_of_bounds_value); + DEBUG("Linear"); + break; + case 2: + Filter::reslice(input, output, trafo, Adapter::AutoOverSample, out_of_bounds_value); + DEBUG("Cubic"); + break; + case 3: + Filter::reslice(input, output, trafo, Adapter::AutoOverSample, out_of_bounds_value); + DEBUG("Sinc"); + break; + default: + throw Exception("Fixme: interpolation value invalid"); } } template - void evaluate_voxelwise_msq ( InType1& in1, - InType2& in2, - MaskType1& in1mask, - MaskType2& in2mask, +void evaluate_voxelwise_msq(InType1 &in1, + InType2 &in2, + MaskType1 &in1mask, + MaskType2 &in2mask, const size_t dimensions, bool use_mask1, bool use_mask2, - ssize_t& n_voxels, - Eigen::VectorXd& sos) { - - using value_type = typename InType1::value_type; - - if (use_mask1 or use_mask2) - n_voxels = 0; - if (use_mask1 and use_mask2) { - if (dimensions == 3) { - for (auto i = Loop() (in1, in2, in1mask, in2mask); i ;++i) - if (in1mask.value() and in2mask.value()) { - ++n_voxels; - meansquared(in1.value(), in2.value(), sos); - } - } else { // 4D - Eigen::Matrix a (in1.size(3)), b (in2.size(3)); - for (auto i = Loop(0, 3) (in1, in2, in1mask, in2mask); i ;++i) { - if (in1mask.value() and in2mask.value()) { - ++n_voxels; - a = in1.row(3); - b = in2.row(3); - meansquared(a, b, sos); - } + ssize_t &n_voxels, + Eigen::VectorXd &sos) { + + using value_type = typename InType1::value_type; + + if (use_mask1 or use_mask2) + n_voxels = 0; + if (use_mask1 and use_mask2) { + if (dimensions == 3) { + for (auto i = Loop()(in1, in2, in1mask, in2mask); i; ++i) + if (in1mask.value() and in2mask.value()) { + ++n_voxels; + meansquared(in1.value(), in2.value(), sos); } - } - } else if (use_mask1) { - if (dimensions == 3) { - for (auto i = Loop() (in1, in2, in1mask); i ;++i) - if (in1mask.value()){ - ++n_voxels; - meansquared(in1.value(), in2.value(), sos); - } - } else { // 4D - Eigen::Matrix a (in1.size(3)), b (in2.size(3)); - for (auto i = Loop(0, 3) (in1, in2, in1mask); i ;++i) { - if (in1mask.value()){ - ++n_voxels; - a = in1.row(3); - b = in2.row(3); - meansquared(a, b, sos); - } + } else { // 4D + Eigen::Matrix a(in1.size(3)), b(in2.size(3)); + for (auto i = Loop(0, 3)(in1, in2, in1mask, in2mask); i; ++i) { + if (in1mask.value() and in2mask.value()) { + ++n_voxels; + a = in1.row(3); + b = in2.row(3); + meansquared(a, b, sos); } } - } else if (use_mask2) { - if (dimensions == 3) { - for (auto i = Loop() (in1, in2, in2mask); i ;++i) - if (in2mask.value()){ - ++n_voxels; - meansquared(in1.value(), in2.value(), sos); - } - } else { // 4D - Eigen::Matrix a (in1.size(3)), b (in2.size(3)); - for (auto i = Loop(0, 3) (in1, in2, in2mask); i ;++i) { - if (in2mask.value()){ - ++n_voxels; - a = in1.row(3); - b = in2.row(3); - meansquared(a, b, sos); - } + } + } else if (use_mask1) { + if (dimensions == 3) { + for (auto i = Loop()(in1, in2, in1mask); i; ++i) + if (in1mask.value()) { + ++n_voxels; + meansquared(in1.value(), in2.value(), sos); + } + } else { // 4D + Eigen::Matrix a(in1.size(3)), b(in2.size(3)); + for (auto i = Loop(0, 3)(in1, in2, in1mask); i; ++i) { + if (in1mask.value()) { + ++n_voxels; + a = in1.row(3); + b = in2.row(3); + meansquared(a, b, sos); } } - } else { - if (dimensions == 3) { - for (auto i = Loop() (in1, in2); i ;++i) + } + } else if (use_mask2) { + if (dimensions == 3) { + for (auto i = Loop()(in1, in2, in2mask); i; ++i) + if (in2mask.value()) { + ++n_voxels; meansquared(in1.value(), in2.value(), sos); - } else { // 4D - Eigen::Matrix a (in1.size(3)), b (in2.size(3)); - for (auto i = Loop(0, 3) (in1, in2); i ;++i) { + } + } else { // 4D + Eigen::Matrix a(in1.size(3)), b(in2.size(3)); + for (auto i = Loop(0, 3)(in1, in2, in2mask); i; ++i) { + if (in2mask.value()) { + ++n_voxels; a = in1.row(3); b = in2.row(3); meansquared(a, b, sos); } } } + } else { + if (dimensions == 3) { + for (auto i = Loop()(in1, in2); i; ++i) + meansquared(in1.value(), in2.value(), sos); + } else { // 4D + Eigen::Matrix a(in1.size(3)), b(in2.size(3)); + for (auto i = Loop(0, 3)(in1, in2); i; ++i) { + a = in1.row(3); + b = in2.row(3); + meansquared(a, b, sos); + } + } } +} -enum MetricType {MeanSquared, CrossCorrelation}; -const char* metric_choices[] = { "diff", "cc", NULL }; - +enum MetricType { MeanSquared, CrossCorrelation }; +const char *metric_choices[] = {"diff", "cc", NULL}; -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and Max Pietsch (maximilian.pietsch@kcl.ac.uk)"; SYNOPSIS = "Computes a dissimilarity metric between two images"; DESCRIPTION - + "Currently only the mean squared difference is fully implemented."; + +"Currently only the mean squared difference is fully implemented."; ARGUMENTS - + Argument ("image1", "the first input image.").type_image_in () - + Argument ("image2", "the second input image.").type_image_in (); + +Argument("image1", "the first input image.").type_image_in() + + Argument("image2", "the second input image.").type_image_in(); OPTIONS - + Option ("space", - "voxel (default): per voxel " - "image1: scanner space of image 1 " - "image2: scanner space of image 2 " - "average: scanner space of the average affine transformation of image 1 and 2 ") - + Argument ("iteration method").type_choice (space_choices) - - + Option ("interp", - "set the interpolation method to use when reslicing (choices: nearest, linear, cubic, sinc. Default: linear).") - + Argument ("method").type_choice (interp_choices) + +Option("space", + "voxel (default): per voxel " + "image1: scanner space of image 1 " + "image2: scanner space of image 2 " + "average: scanner space of the average affine transformation of image 1 and 2 ") + + Argument("iteration method").type_choice(space_choices) - + Option ("metric", - "define the dissimilarity metric used to calculate the cost. " - "Choices: diff (squared differences), cc (non-normalised negative cross correlation aka negative cross covariance). Default: diff). " - "cc is only implemented for -space average and -interp linear and cubic.") - + Argument ("method").type_choice (metric_choices) + + Option("interp", + "set the interpolation method to use when reslicing (choices: nearest, linear, cubic, sinc. Default: " + "linear).") + + Argument("method").type_choice(interp_choices) - + Option ("mask1", "mask for image 1") - + Argument ("image").type_image_in () + + Option("metric", + "define the dissimilarity metric used to calculate the cost. " + "Choices: diff (squared differences), cc (non-normalised negative cross correlation aka negative cross " + "covariance). Default: diff). " + "cc is only implemented for -space average and -interp linear and cubic.") + + Argument("method").type_choice(metric_choices) - + Option ("mask2", "mask for image 2") - + Argument ("image").type_image_in () + + Option("mask1", "mask for image 1") + Argument("image").type_image_in() - + Option ("nonormalisation", - "do not normalise the dissimilarity metric to the number of voxels.") + + Option("mask2", "mask for image 2") + Argument("image").type_image_in() - + Option ("overlap", - "output number of voxels that were used."); + + Option("nonormalisation", "do not normalise the dissimilarity metric to the number of voxels.") + + Option("overlap", "output number of voxels that were used."); } using value_type = double; using MaskType = Image; -void run () -{ - int space = 0; // voxel - auto opt = get_options ("space"); +void run() { + int space = 0; // voxel + auto opt = get_options("space"); if (opt.size()) space = opt[0][0]; - int interp = 1; // linear - opt = get_options ("interp"); + int interp = 1; // linear + opt = get_options("interp"); if (opt.size()) interp = opt[0][0]; MetricType metric_type = MetricType::MeanSquared; - opt = get_options ("metric"); + opt = get_options("metric"); if (opt.size()) { if (int(opt[0][0]) == 1) { // CC if (space != 3) - throw Exception ("CC metric only implemented for use in average space"); + throw Exception("CC metric only implemented for use in average space"); if (interp != 1 and interp != 2) - throw Exception ("CC metric only implemented for use with linear and cubic interpolation"); + throw Exception("CC metric only implemented for use with linear and cubic interpolation"); metric_type = MetricType::CrossCorrelation; } } - auto input1 = Image::open (argument[0]).with_direct_io (Stride::contiguous_along_axis (3)); - auto input2 = Image::open (argument[1]).with_direct_io (Stride::contiguous_along_axis (3)); + auto input1 = Image::open(argument[0]).with_direct_io(Stride::contiguous_along_axis(3)); + auto input2 = Image::open(argument[1]).with_direct_io(Stride::contiguous_along_axis(3)); const size_t dimensions = input1.ndim(); if (input1.ndim() != input2.ndim()) - throw Exception ("both images have to have the same number of dimensions"); - DEBUG ("dimensions: " + str(dimensions)); - if (dimensions > 4) throw Exception ("images have to be 3 or 4 dimensional"); + throw Exception("both images have to have the same number of dimensions"); + DEBUG("dimensions: " + str(dimensions)); + if (dimensions > 4) + throw Exception("images have to be 3 or 4 dimensional"); if (dimensions != 3 and metric_type == MetricType::CrossCorrelation) - throw Exception ("CC metric requires 3D images"); + throw Exception("CC metric requires 3D images"); size_t volumes(1); if (dimensions == 4) { volumes = input1.size(3); - if (input1.size(3) != input2.size(3)){ - throw Exception ("both images have to have the same number of volumes"); + if (input1.size(3) != input2.size(3)) { + throw Exception("both images have to have the same number of volumes"); } } - INFO ("volumes: " + str(volumes)); + INFO("volumes: " + str(volumes)); MaskType mask1; - bool use_mask1 = get_options ("mask1").size()==1; + bool use_mask1 = get_options("mask1").size() == 1; if (use_mask1) { - mask1 = Image::open (get_options ("mask1")[0][0]); - if (mask1.ndim() != 3) throw Exception ("mask has to be 3D"); + mask1 = Image::open(get_options("mask1")[0][0]); + if (mask1.ndim() != 3) + throw Exception("mask has to be 3D"); } MaskType mask2; - bool use_mask2 = get_options ("mask2").size()==1; - if (use_mask2){ - mask2 = Image::open (get_options ("mask2")[0][0]); - if (mask2.ndim() != 3) throw Exception ("mask has to be 3D"); + bool use_mask2 = get_options("mask2").size() == 1; + if (use_mask2) { + mask2 = Image::open(get_options("mask2")[0][0]); + if (mask2.ndim() != 3) + throw Exception("mask has to be 3D"); } bool nonormalisation = false; - if (get_options ("nonormalisation").size()) + if (get_options("nonormalisation").size()) nonormalisation = true; ssize_t n_voxels = input1.size(0) * input1.size(1) * input1.size(2); value_type out_of_bounds_value = 0.0; - Eigen::Matrix sos = Eigen::Matrix::Zero (volumes, 1); - if (space==0) { - INFO ("per-voxel"); - check_dimensions (input1, input2); + Eigen::Matrix sos = Eigen::Matrix::Zero(volumes, 1); + if (space == 0) { + INFO("per-voxel"); + check_dimensions(input1, input2); if (!use_mask1 and !use_mask2) n_voxels = input1.size(0) * input1.size(1) * input1.size(2); - evaluate_voxelwise_msq (input1, input2, mask1, mask2, dimensions, use_mask1, use_mask2, n_voxels, sos); + evaluate_voxelwise_msq(input1, input2, mask1, mask2, dimensions, use_mask1, use_mask2, n_voxels, sos); } else { - DEBUG ("scanner space"); - auto output1 = Header::scratch (input1, "-").get_image(); - auto output2 = Header::scratch (input2, "-").get_image(); + DEBUG("scanner space"); + auto output1 = Header::scratch(input1, "-").get_image(); + auto output2 = Header::scratch(input2, "-").get_image(); MaskType output1mask; MaskType output2mask; - if (space == 1){ - INFO ("space: image 1"); + if (space == 1) { + INFO("space: image 1"); output1 = input1; output1mask = mask1; - output2 = Header::scratch (input1, "-").get_image(); - output2mask = Header::scratch (input1, "-").get_image(); + output2 = Header::scratch(input1, "-").get_image(); + output2mask = Header::scratch(input1, "-").get_image(); { - LogLevelLatch log_level (0); + LogLevelLatch log_level(0); reslice(interp, input2, output2, Adapter::NoTransform, Adapter::AutoOverSample, out_of_bounds_value); if (use_mask2) - Filter::reslice (mask2, output2mask, Adapter::NoTransform, Adapter::AutoOverSample, 0); + Filter::reslice(mask2, output2mask, Adapter::NoTransform, Adapter::AutoOverSample, 0); } - evaluate_voxelwise_msq (output1, output2, output1mask, output2mask, dimensions, use_mask1, use_mask2, n_voxels, sos); + evaluate_voxelwise_msq( + output1, output2, output1mask, output2mask, dimensions, use_mask1, use_mask2, n_voxels, sos); } if (space == 2) { - INFO ("space: image 2"); - output1 = Header::scratch (input2, "-").get_image(); - output1mask = Header::scratch (input2, "-").get_image(); + INFO("space: image 2"); + output1 = Header::scratch(input2, "-").get_image(); + output1mask = Header::scratch(input2, "-").get_image(); output2 = input2; output2mask = mask2; { - LogLevelLatch log_level (0); + LogLevelLatch log_level(0); reslice(interp, input1, output1, Adapter::NoTransform, Adapter::AutoOverSample, out_of_bounds_value); if (use_mask1) - Filter::reslice (mask1, output1mask, Adapter::NoTransform, Adapter::AutoOverSample, 0); + Filter::reslice(mask1, output1mask, Adapter::NoTransform, Adapter::AutoOverSample, 0); } n_voxels = input2.size(0) * input2.size(1) * input2.size(2); - evaluate_voxelwise_msq (output1, output2, output1mask, output2mask, dimensions, use_mask1, use_mask2, n_voxels, sos); + evaluate_voxelwise_msq( + output1, output2, output1mask, output2mask, dimensions, use_mask1, use_mask2, n_voxels, sos); } if (space == 3) { - INFO ("space: average space"); + INFO("space: average space"); using ImageType1 = Image; using ImageType2 = Image; @@ -335,143 +338,148 @@ void run () n_voxels = 0; Registration::Transform::Rigid transform; - Header midway_image_header = compute_minimum_average_header (input1, input2); - - using LinearInterpolatorType1 = Interp::LinearInterp, Interp::LinearInterpProcessingType::Value>; - using LinearInterpolatorType2 = Interp::LinearInterp, Interp::LinearInterpProcessingType::Value>; - using CubicInterpolatorType1 = Interp::SplineInterp, Math::SplineProcessingType::Value>; - using CubicInterpolatorType2 = Interp::SplineInterp, Math::SplineProcessingType::Value>; + Header midway_image_header = compute_minimum_average_header(input1, input2); + + using LinearInterpolatorType1 = + Interp::LinearInterp, Interp::LinearInterpProcessingType::Value>; + using LinearInterpolatorType2 = + Interp::LinearInterp, Interp::LinearInterpProcessingType::Value>; + using CubicInterpolatorType1 = Interp::SplineInterp, + Math::SplineProcessingType::Value>; + using CubicInterpolatorType2 = Interp::SplineInterp, + Math::SplineProcessingType::Value>; using MaskInterpolatorType1 = Interp::Nearest>; using MaskInterpolatorType2 = Interp::Nearest>; using ProcessedImageType = Image; using ProcessedMaskType = Image; - using LinearParamType = Registration::Metric::Params < - Registration::Transform::Rigid, - ImageType1, - ImageType2, - ImageTypeM, - MaskType, - MaskType, - LinearInterpolatorType1, - LinearInterpolatorType2, - MaskInterpolatorType1, - MaskInterpolatorType2, - Image, - Interp::LinearInterp, - ProcessedMaskType, - Interp::Nearest - >; - using CubicParamType = Registration::Metric::Params < - Registration::Transform::Rigid, - ImageType1, - ImageType2, - ImageTypeM, - MaskType, - MaskType, - CubicInterpolatorType1, - CubicInterpolatorType2, - MaskInterpolatorType1, - MaskInterpolatorType2, - ProcessedImageType, - Interp::LinearInterp, - ProcessedMaskType, - Interp::Nearest - >; - - ImageTypeM midway_image (midway_image_header); - - Eigen::VectorXd gradient = Eigen::VectorXd::Zero(1); - // interp == 1 or 2, metric, dimensions, interp - if (interp == 1 or interp == 2) { - if ( metric_type == MetricType::MeanSquared ) { - if ( dimensions == 3 ) { - Registration::Metric::MeanSquaredNoGradient metric; - if (interp == 1) { - LinearParamType parameters (transform, input1, input2, midway_image, mask1, mask2); - Registration::Metric::ThreadKernel kernel - (metric, parameters, sos, gradient, &n_voxels); - ThreadedLoop (parameters.midway_image, 0, 3).run (kernel); - } else if (interp == 2) { - CubicParamType parameters (transform, input1, input2, midway_image, mask1, mask2); - Registration::Metric::ThreadKernel kernel - (metric, parameters, sos, gradient, &n_voxels); - ThreadedLoop (parameters.midway_image, 0, 3).run (kernel); - } - } else if ( dimensions == 4) { - Registration::Metric::MeanSquaredVectorNoGradient4D - metric ( input1, input2 ); - if (interp == 1) { - LinearParamType parameters (transform, input1, input2, midway_image, mask1, mask2); - Registration::Metric::ThreadKernel kernel - (metric, parameters, sos, gradient, &n_voxels); - ThreadedLoop (parameters.midway_image, 0, 3).run (kernel); - } else if (interp == 2) { - CubicParamType parameters (transform, input1, input2, midway_image, mask1, mask2); - Registration::Metric::ThreadKernel kernel - (metric, parameters, sos, gradient, &n_voxels); - ThreadedLoop (parameters.midway_image, 0, 3).run (kernel); - } else { throw Exception ("Fixme: invalid metric choice "); } + using LinearParamType = Registration::Metric::Params< + Registration::Transform::Rigid, + ImageType1, + ImageType2, + ImageTypeM, + MaskType, + MaskType, + LinearInterpolatorType1, + LinearInterpolatorType2, + MaskInterpolatorType1, + MaskInterpolatorType2, + Image, + Interp::LinearInterp, + ProcessedMaskType, + Interp::Nearest>; + using CubicParamType = Registration::Metric::Params< + Registration::Transform::Rigid, + ImageType1, + ImageType2, + ImageTypeM, + MaskType, + MaskType, + CubicInterpolatorType1, + CubicInterpolatorType2, + MaskInterpolatorType1, + MaskInterpolatorType2, + ProcessedImageType, + Interp::LinearInterp, + ProcessedMaskType, + Interp::Nearest>; + + ImageTypeM midway_image(midway_image_header); + + Eigen::VectorXd gradient = Eigen::VectorXd::Zero(1); + // interp == 1 or 2, metric, dimensions, interp + if (interp == 1 or interp == 2) { + if (metric_type == MetricType::MeanSquared) { + if (dimensions == 3) { + Registration::Metric::MeanSquaredNoGradient metric; + if (interp == 1) { + LinearParamType parameters(transform, input1, input2, midway_image, mask1, mask2); + Registration::Metric::ThreadKernel kernel( + metric, parameters, sos, gradient, &n_voxels); + ThreadedLoop(parameters.midway_image, 0, 3).run(kernel); + } else if (interp == 2) { + CubicParamType parameters(transform, input1, input2, midway_image, mask1, mask2); + Registration::Metric::ThreadKernel kernel( + metric, parameters, sos, gradient, &n_voxels); + ThreadedLoop(parameters.midway_image, 0, 3).run(kernel); } - } else if ( metric_type == MetricType::CrossCorrelation) { - Registration::Metric::CrossCorrelationNoGradient metric; + } else if (dimensions == 4) { + Registration::Metric::MeanSquaredVectorNoGradient4D metric(input1, input2); if (interp == 1) { - LinearParamType parameters (transform, input1, input2, midway_image, mask1, mask2); - metric.precompute (parameters); - Registration::Metric::ThreadKernel kernel - (metric, parameters, sos, gradient, &n_voxels); - ThreadedLoop (parameters.processed_image, 0, 3).run (kernel); + LinearParamType parameters(transform, input1, input2, midway_image, mask1, mask2); + Registration::Metric::ThreadKernel kernel( + metric, parameters, sos, gradient, &n_voxels); + ThreadedLoop(parameters.midway_image, 0, 3).run(kernel); } else if (interp == 2) { - CubicParamType parameters (transform, input1, input2, midway_image, mask1, mask2); - metric.precompute (parameters); - Registration::Metric::ThreadKernel kernel - (metric, parameters, sos, gradient, &n_voxels); - ThreadedLoop (parameters.processed_image, 0, 3).run (kernel); + CubicParamType parameters(transform, input1, input2, midway_image, mask1, mask2); + Registration::Metric::ThreadKernel kernel( + metric, parameters, sos, gradient, &n_voxels); + ThreadedLoop(parameters.midway_image, 0, 3).run(kernel); + } else { + throw Exception("Fixme: invalid metric choice "); } } + } else if (metric_type == MetricType::CrossCorrelation) { + Registration::Metric::CrossCorrelationNoGradient metric; + if (interp == 1) { + LinearParamType parameters(transform, input1, input2, midway_image, mask1, mask2); + metric.precompute(parameters); + Registration::Metric::ThreadKernel kernel( + metric, parameters, sos, gradient, &n_voxels); + ThreadedLoop(parameters.processed_image, 0, 3).run(kernel); + } else if (interp == 2) { + CubicParamType parameters(transform, input1, input2, midway_image, mask1, mask2); + metric.precompute(parameters); + Registration::Metric::ThreadKernel kernel( + metric, parameters, sos, gradient, &n_voxels); + ThreadedLoop(parameters.processed_image, 0, 3).run(kernel); + } + } } else { // interp != 1 or 2 --> reslice and run voxel-wise comparison if (metric_type != MetricType::MeanSquared) - throw Exception ("Fixme: invalid metric choice "); - output1mask = Header::scratch (midway_image_header, "-").get_image(); - output2mask = Header::scratch (midway_image_header, "-").get_image(); + throw Exception("Fixme: invalid metric choice "); + output1mask = Header::scratch(midway_image_header, "-").get_image(); + output2mask = Header::scratch(midway_image_header, "-").get_image(); Header new_header; new_header.ndim() = input1.ndim(); - for (ssize_t dim=0; dim < 3; ++dim){ + for (ssize_t dim = 0; dim < 3; ++dim) { new_header.size(dim) = midway_image_header.size(dim); new_header.spacing(dim) = midway_image_header.spacing(dim); } - if (dimensions == 4 ){ + if (dimensions == 4) { new_header.size(3) = input1.size(3); new_header.spacing(3) = input1.spacing(3); // doesn't matter what spacing(3) is } new_header.transform() = midway_image_header.transform(); - output1 = Header::scratch (new_header,"-").get_image(); - output2 = Header::scratch (new_header,"-").get_image(); + output1 = Header::scratch(new_header, "-").get_image(); + output2 = Header::scratch(new_header, "-").get_image(); { - LogLevelLatch log_level (0); + LogLevelLatch log_level(0); reslice(interp, input1, output1, Adapter::NoTransform, Adapter::AutoOverSample, out_of_bounds_value); reslice(interp, input2, output2, Adapter::NoTransform, Adapter::AutoOverSample, out_of_bounds_value); if (use_mask1) - Filter::reslice (mask1, output1mask, Adapter::NoTransform, Adapter::AutoOverSample, 0); + Filter::reslice(mask1, output1mask, Adapter::NoTransform, Adapter::AutoOverSample, 0); if (use_mask2) - Filter::reslice (mask2, output2mask, Adapter::NoTransform, Adapter::AutoOverSample, 0); + Filter::reslice(mask2, output2mask, Adapter::NoTransform, Adapter::AutoOverSample, 0); } n_voxels = output1.size(0) * output1.size(1) * output1.size(2); - evaluate_voxelwise_msq (output1, output2, output1mask, output2mask, dimensions, use_mask1, use_mask2, n_voxels, sos); + evaluate_voxelwise_msq( + output1, output2, output1mask, output2mask, dimensions, use_mask1, use_mask2, n_voxels, sos); } } // "average space" } - DEBUG ("n_voxels:" + str(n_voxels)); - if (n_voxels==0) + DEBUG("n_voxels:" + str(n_voxels)); + if (n_voxels == 0) WARN("number of overlapping voxels is zero"); if (!nonormalisation) sos.array() /= static_cast(n_voxels); std::cout << str(sos.transpose()); - if (get_options ("overlap").size()) + if (get_options("overlap").size()) std::cout << " " << str(n_voxels); std::cout << std::endl; } - diff --git a/cmd/mrregister.cpp b/cmd/mrregister.cpp index fca87b3be0..6a5d4b9934 100644 --- a/cmd/mrregister.cpp +++ b/cmd/mrregister.cpp @@ -15,246 +15,271 @@ */ #include "command.h" +#include "dwi/directions/predefined.h" +#include "file/matrix.h" +#include "file/nifti_utils.h" +#include "filter/reslice.h" #include "image.h" #include "image_helpers.h" -#include "filter/reslice.h" #include "interp/cubic.h" -#include "transform.h" -#include "registration/multi_contrast.h" +#include "math/SH.h" +#include "math/average_space.h" +#include "math/sphere.h" #include "registration/linear.h" -#include "registration/nonlinear.h" #include "registration/metric/demons.h" -#include "registration/metric/mean_squared.h" #include "registration/metric/difference_robust.h" #include "registration/metric/local_cross_correlation.h" +#include "registration/metric/mean_squared.h" +#include "registration/multi_contrast.h" +#include "registration/nonlinear.h" #include "registration/transform/affine.h" #include "registration/transform/rigid.h" -#include "dwi/directions/predefined.h" -#include "file/matrix.h" -#include "file/nifti_utils.h" -#include "math/average_space.h" -#include "math/SH.h" -#include "math/sphere.h" - +#include "transform.h" using namespace MR; using namespace App; -const char* transformation_choices[] = { "rigid", "affine", "nonlinear", "rigid_affine", "rigid_nonlinear", "affine_nonlinear", "rigid_affine_nonlinear", NULL }; +const char *transformation_choices[] = {"rigid", + "affine", + "nonlinear", + "rigid_affine", + "rigid_nonlinear", + "affine_nonlinear", + "rigid_affine_nonlinear", + NULL}; const OptionGroup multiContrastOptions = - OptionGroup ("Multi-contrast options") - + Option ("mc_weights", "relative weight of images used for multi-contrast registration. Default: 1.0 (equal weighting)") - + Argument ("weights").type_sequence_float (); - + OptionGroup("Multi-contrast options") + + Option("mc_weights", + "relative weight of images used for multi-contrast registration. Default: 1.0 (equal weighting)") + + Argument("weights").type_sequence_float(); -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) & Max Pietsch (maximilian.pietsch@kcl.ac.uk)"; SYNOPSIS = "Register two images together using a symmetric rigid, affine or non-linear transformation model"; DESCRIPTION - + "By default this application will perform an affine, followed by non-linear registration." + +"By default this application will perform an affine, followed by non-linear registration." - + "FOD registration (with apodised point spread reorientation) will be performed by default if the number of volumes " - "in the 4th dimension equals the number of coefficients in an antipodally symmetric spherical harmonic series (e.g. 6, 15, 28 etc). " + + "FOD registration (with apodised point spread reorientation) will be performed by default if the number of " + "volumes " + "in the 4th dimension equals the number of coefficients in an antipodally symmetric spherical harmonic series " + "(e.g. 6, 15, 28 etc). " "The -no_reorientation option can be used to force reorientation off if required." // TODO link to 5D warp file format documentation + "Non-linear registration computes warps to map from both image1->image2 and image2->image1. " - "Similar to Avants (2008) Med Image Anal. 12(1): 26–41, registration is performed by matching both the image1 and image2 in a 'midway space'. " - "Warps can be saved as two deformation fields that map directly between image1->image2 and image2->image1, or if using -nl_warp_full as a single 5D file " - "that stores all 4 warps image1->mid->image2, and image2->mid->image1. The 5D warp format stores x,y,z deformations in the 4th dimension, and uses the 5th dimension " - "to index the 4 warps. The affine transforms estimated (to midway space) are also stored as comments in the image header. The 5D warp file can be used to reinitialise " - "subsequent registrations, in addition to transforming images to midway space (e.g. for intra-subject alignment in a 2-time-point longitudinal analysis)."; + "Similar to Avants (2008) Med Image Anal. 12(1): 26–41, registration is performed by matching both the image1 " + "and image2 in a 'midway space'. " + "Warps can be saved as two deformation fields that map directly between image1->image2 and image2->image1, or " + "if using -nl_warp_full as a single 5D file " + "that stores all 4 warps image1->mid->image2, and image2->mid->image1. The 5D warp format stores x,y,z " + "deformations in the 4th dimension, and uses the 5th dimension " + "to index the 4 warps. The affine transforms estimated (to midway space) are also stored as comments in the " + "image header. The 5D warp file can be used to reinitialise " + "subsequent registrations, in addition to transforming images to midway space (e.g. for intra-subject " + "alignment in a 2-time-point longitudinal analysis)."; REFERENCES - + "* If FOD registration is being performed:\n" - "Raffelt, D.; Tournier, J.-D.; Fripp, J; Crozier, S.; Connelly, A. & Salvado, O. " // Internal - "Symmetric diffeomorphic registration of fibre orientation distributions. " - "NeuroImage, 2011, 56(3), 1171-1180" - - + "Raffelt, D.; Tournier, J.-D.; Crozier, S.; Connelly, A. & Salvado, O. " // Internal - "Reorientation of fiber orientation distributions using apodized point spread functions. " - "Magnetic Resonance in Medicine, 2012, 67, 844-855"; + +"* If FOD registration is being performed:\n" + "Raffelt, D.; Tournier, J.-D.; Fripp, J; Crozier, S.; Connelly, A. & Salvado, O. " // Internal + "Symmetric diffeomorphic registration of fibre orientation distributions. " + "NeuroImage, 2011, 56(3), 1171-1180" + + "Raffelt, D.; Tournier, J.-D.; Crozier, S.; Connelly, A. & Salvado, O. " // Internal + "Reorientation of fiber orientation distributions using apodized point spread functions. " + "Magnetic Resonance in Medicine, 2012, 67, 844-855"; ARGUMENTS - + Argument ("image1 image2", "input image 1 ('moving') and input image 2 ('template')").type_image_in() - + Argument ("contrast1 contrast2", "optional list of additional input images used as additional contrasts. " - "Can be used multiple times. contrastX and imageX must share the same coordinate system. ").type_image_in().optional().allow_multiple(); + +Argument("image1 image2", "input image 1 ('moving') and input image 2 ('template')").type_image_in() + + Argument("contrast1 contrast2", + "optional list of additional input images used as additional contrasts. " + "Can be used multiple times. contrastX and imageX must share the same coordinate system. ") + .type_image_in() + .optional() + .allow_multiple(); OPTIONS - + Option ("type", "the registration type. Valid choices are: " - "rigid, affine, nonlinear, rigid_affine, rigid_nonlinear, affine_nonlinear, rigid_affine_nonlinear (Default: affine_nonlinear)") - + Argument ("choice").type_choice (transformation_choices) + +Option("type", + "the registration type. Valid choices are: " + "rigid, affine, nonlinear, rigid_affine, rigid_nonlinear, affine_nonlinear, rigid_affine_nonlinear (Default: " + "affine_nonlinear)") + + Argument("choice").type_choice(transformation_choices) - + Option ("transformed", "image1 after registration transformed and regridded to the space of image2. " - "Note that -transformed needs to be repeated for each contrast if multi-contrast registration is used.").allow_multiple() - + Argument ("image").type_image_out () + + Option("transformed", + "image1 after registration transformed and regridded to the space of image2. " + "Note that -transformed needs to be repeated for each contrast if multi-contrast registration is used.") + .allow_multiple() + + Argument("image").type_image_out() - + Option ("transformed_midway", "image1 and image2 after registration transformed and regridded to the midway space. " - "Note that -transformed_midway needs to be repeated for each contrast if multi-contrast registration is used.").allow_multiple() - + Argument ("image1_transformed").type_image_out () - + Argument ("image2_transformed").type_image_out () + + Option("transformed_midway", + "image1 and image2 after registration transformed and regridded to the midway space. " + "Note that -transformed_midway needs to be repeated for each contrast if multi-contrast registration is " + "used.") + .allow_multiple() + + Argument("image1_transformed").type_image_out() + Argument("image2_transformed").type_image_out() - + Option ("mask1", "a mask to define the region of image1 to use for optimisation.") - + Argument ("filename").type_image_in () + + Option("mask1", "a mask to define the region of image1 to use for optimisation.") + + Argument("filename").type_image_in() - + Option ("mask2", "a mask to define the region of image2 to use for optimisation.") - + Argument ("filename").type_image_in () + + Option("mask2", "a mask to define the region of image2 to use for optimisation.") + + Argument("filename").type_image_in() - + Option("nan", "use NaN as out of bounds value. (Default: 0.0)") + + Option("nan", "use NaN as out of bounds value. (Default: 0.0)") - + Registration::rigid_options + + Registration::rigid_options - + Registration::affine_options + + Registration::affine_options - + Registration::adv_init_options + + Registration::adv_init_options - + Registration::lin_stage_options + + Registration::lin_stage_options - + Registration::nonlinear_options + + Registration::nonlinear_options - + Registration::fod_options + + Registration::fod_options - + multiContrastOptions + + multiContrastOptions - + DataType::options(); + + DataType::options(); } using value_type = double; -void run () { +void run() { vector
input1, input2; const size_t n_images = argument.size() / 2; { // parse arguments and load input headers if (n_images * 2 != argument.size()) { std::string err; - for (const auto & a : argument) + for (const auto &a : argument) err += " " + str(a); - throw Exception ("unexpected number of input images. arguments:" + err); + throw Exception("unexpected number of input images. arguments:" + err); } bool is1 = true; - for (const auto& arg : argument) { + for (const auto &arg : argument) { if (is1) - input1.push_back (Header::open (str(arg))); + input1.push_back(Header::open(str(arg))); else - input2.push_back (Header::open (str(arg))); + input2.push_back(Header::open(str(arg))); is1 = !is1; } } - assert (input1.size() == n_images); + assert(input1.size() == n_images); if (input1.size() != input2.size()) - throw Exception ("require same number of input images for image 1 and image 2"); + throw Exception("require same number of input images for image 1 and image 2"); - for (size_t i=0; i registration without reorientation. // will be set to false if registration of all input SH images has lmax==0 bool do_reorientation = !reorientation_forbidden; Eigen::MatrixXd directions_cartesian; - opt = get_options ("directions"); + opt = get_options("directions"); if (opt.size()) - directions_cartesian = Math::Sphere::spherical2cartesian (File::Matrix::load_matrix (opt[0][0])).transpose(); + directions_cartesian = Math::Sphere::spherical2cartesian(File::Matrix::load_matrix(opt[0][0])).transpose(); // check header transformations for equality Eigen::MatrixXd trafo = MR::Transform(input1[0]).scanner2voxel.linear(); - for (size_t i=1; i mc_params (n_images); - for (auto& mc : mc_params) { + vector mc_params(n_images); + for (auto &mc : mc_params) { mc.do_reorientation = do_reorientation; } // set parameters for each contrast - for (size_t i=0; i0) check_dimensions (input1[i], input1[i-1], 0, 3); - if (i>0) check_dimensions (input2[i], input2[i-1], 0, 3); + if (i > 0) + check_dimensions(input1[i], input1[i - 1], 0, 3); + if (i > 0) + check_dimensions(input2[i], input2[i - 1], 0, 3); if ((input1[i].ndim() != 3) and (input1[i].ndim() != 4)) - throw Exception ("image dimensionality other than 3 or 4 are not supported. image " + - str(input1[i].name()) + " is " + str(input1[i].ndim()) + " dimensional"); + throw Exception("image dimensionality other than 3 or 4 are not supported. image " + str(input1[i].name()) + + " is " + str(input1[i].ndim()) + " dimensional"); const size_t nvols1 = input1[i].ndim() == 3 ? 1 : input1[i].size(3); const size_t nvols2 = input2[i].ndim() == 3 ? 1 : input2[i].size(3); if (nvols1 != nvols2) - throw Exception ("input images do not have the same number of volumes: " + str(input2[i].name()) + " and " + str(input1[i].name())); + throw Exception("input images do not have the same number of volumes: " + str(input2[i].name()) + " and " + + str(input1[i].name())); // set do_reorientation and image_lmax if (nvols1 == 1) { // 3D or one volume mc_params[i].do_reorientation = false; mc_params[i].image_lmax = 0; - CONSOLE ("3D input pair "+input1[i].name()+", "+input2[i].name()); + CONSOLE("3D input pair " + input1[i].name() + ", " + input2[i].name()); } else { // more than one volume if (do_reorientation && nvols1 > 1 && SH::NforL(SH::LforN(nvols1)) == nvols1) { - CONSOLE ("SH image input pair "+input1[i].name()+", "+input2[i].name()); + CONSOLE("SH image input pair " + input1[i].name() + ", " + input2[i].name()); mc_params[i].do_reorientation = true; - mc_params[i].image_lmax = Math::SH::LforN (nvols1); + mc_params[i].image_lmax = Math::SH::LforN(nvols1); if (!directions_cartesian.cols()) - directions_cartesian = Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_60()).transpose(); + directions_cartesian = + Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_60()).transpose(); } else { - CONSOLE ("4D scalar input pair "+input1[i].name()+", "+input2[i].name()); + CONSOLE("4D scalar input pair " + input1[i].name() + ", " + input2[i].name()); mc_params[i].do_reorientation = false; mc_params[i].image_lmax = 0; } @@ -264,528 +289,542 @@ void run () { mc_params[i].image_nvols = input1[i].ndim() < 4 ? 1 : input1[i].size(3); } - ssize_t max_mc_image_lmax = std::max_element(mc_params.begin(), mc_params.end(), - [](const Registration::MultiContrastSetting& x, const Registration::MultiContrastSetting& y) - {return x.lmax < y.lmax;})->lmax; - - do_reorientation = std::any_of(mc_params.begin(), mc_params.end(), - [](Registration::MultiContrastSetting const& i){return i.do_reorientation;}); + ssize_t max_mc_image_lmax = + std::max_element(mc_params.begin(), + mc_params.end(), + [](const Registration::MultiContrastSetting &x, const Registration::MultiContrastSetting &y) { + return x.lmax < y.lmax; + }) + ->lmax; + + do_reorientation = std::any_of(mc_params.begin(), mc_params.end(), [](Registration::MultiContrastSetting const &i) { + return i.do_reorientation; + }); if (do_reorientation) CONSOLE("performing FOD registration"); if (!do_reorientation and directions_cartesian.cols()) - WARN ("-directions option ignored since no FOD reorientation is being performed"); + WARN("-directions option ignored since no FOD reorientation is being performed"); - INFO ("maximum input lmax: "+str(max_mc_image_lmax)); + INFO("maximum input lmax: " + str(max_mc_image_lmax)); - opt = get_options ("transformed"); + opt = get_options("transformed"); vector im1_transformed_paths; if (opt.size()) { if (opt.size() > n_images) - throw Exception ("number of -transformed images exceeds number of contrasts"); + throw Exception("number of -transformed images exceeds number of contrasts"); if (opt.size() != n_images) - WARN ("number of -transformed images lower than number of contrasts"); + WARN("number of -transformed images lower than number of contrasts"); for (size_t c = 0; c < opt.size(); c++) { - Registration::check_image_output (opt[c][0], input2[c]); + Registration::check_image_output(opt[c][0], input2[c]); im1_transformed_paths.push_back(opt[c][0]); - INFO (input1[c].name() + ", transformed to space of image2, will be written to " + im1_transformed_paths[c]); + INFO(input1[c].name() + ", transformed to space of image2, will be written to " + im1_transformed_paths[c]); } } - vector input1_midway_transformed_paths; vector input2_midway_transformed_paths; - opt = get_options ("transformed_midway"); + opt = get_options("transformed_midway"); if (opt.size()) { if (opt.size() > n_images) - throw Exception ("number of -transformed_midway images exceeds number of contrasts"); + throw Exception("number of -transformed_midway images exceeds number of contrasts"); if (opt.size() != n_images) - WARN ("number of -transformed_midway images lower than number of contrasts"); + WARN("number of -transformed_midway images lower than number of contrasts"); for (size_t c = 0; c < opt.size(); c++) { - Registration::check_image_output (opt[c][0], input2[c]); + Registration::check_image_output(opt[c][0], input2[c]); input1_midway_transformed_paths.push_back(opt[c][0]); - INFO (input1[c].name() + ", transformed to midway space, will be written to " + input1_midway_transformed_paths[c]); - Registration::check_image_output (opt[c][1], input1[c]); + INFO(input1[c].name() + ", transformed to midway space, will be written to " + + input1_midway_transformed_paths[c]); + Registration::check_image_output(opt[c][1], input1[c]); input2_midway_transformed_paths.push_back(opt[c][1]); - INFO (input2[c].name() + ", transformed to midway space, will be written to " + input2_midway_transformed_paths[c]); + INFO(input2[c].name() + ", transformed to midway space, will be written to " + + input2_midway_transformed_paths[c]); } } - opt = get_options ("mask1"); + opt = get_options("mask1"); Image im1_mask; - if (opt.size ()) { + if (opt.size()) { im1_mask = Image::open(opt[0][0]); - check_dimensions (input1[0], im1_mask, 0, 3); + check_dimensions(input1[0], im1_mask, 0, 3); } - opt = get_options ("mask2"); + opt = get_options("mask2"); Image im2_mask; - if (opt.size ()) { + if (opt.size()) { im2_mask = Image::open(opt[0][0]); - check_dimensions (input2[0], im2_mask, 0, 3); + check_dimensions(input2[0], im2_mask, 0, 3); } // Out of bounds value value_type out_of_bounds_value = 0.0; - opt = get_options ("nan"); + opt = get_options("nan"); if (opt.size()) out_of_bounds_value = NAN; - // ****** RIGID REGISTRATION OPTIONS ******* Registration::Linear rigid_registration; - opt = get_options ("rigid"); + opt = get_options("rigid"); bool output_rigid = false; std::string rigid_filename; if (opt.size()) { if (!do_rigid) - throw Exception ("rigid transformation output requested when no rigid registration is requested"); + throw Exception("rigid transformation output requested when no rigid registration is requested"); output_rigid = true; - rigid_filename = std::string (opt[0][0]); + rigid_filename = std::string(opt[0][0]); } - opt = get_options ("rigid_1tomidway"); + opt = get_options("rigid_1tomidway"); bool output_rigid_1tomid = false; std::string rigid_1tomid_filename; if (opt.size()) { - if (!do_rigid) - throw Exception ("midway rigid transformation output requested when no rigid registration is requested"); - output_rigid_1tomid = true; - rigid_1tomid_filename = std::string (opt[0][0]); + if (!do_rigid) + throw Exception("midway rigid transformation output requested when no rigid registration is requested"); + output_rigid_1tomid = true; + rigid_1tomid_filename = std::string(opt[0][0]); } - opt = get_options ("rigid_2tomidway"); + opt = get_options("rigid_2tomidway"); bool output_rigid_2tomid = false; std::string rigid_2tomid_filename; if (opt.size()) { - if (!do_rigid) - throw Exception ("midway rigid transformation output requested when no rigid registration is requested"); - output_rigid_2tomid = true; - rigid_2tomid_filename = std::string (opt[0][0]); + if (!do_rigid) + throw Exception("midway rigid transformation output requested when no rigid registration is requested"); + output_rigid_2tomid = true; + rigid_2tomid_filename = std::string(opt[0][0]); } Registration::Transform::Rigid rigid; - opt = get_options ("rigid_init_matrix"); + opt = get_options("rigid_init_matrix"); bool init_rigid_matrix_set = false; if (opt.size()) { init_rigid_matrix_set = true; Eigen::Vector3d centre; - transform_type rigid_transform = File::Matrix::load_transform (opt[0][0], centre); - rigid.set_transform (rigid_transform); + transform_type rigid_transform = File::Matrix::load_transform(opt[0][0], centre); + rigid.set_transform(rigid_transform); if (!std::isfinite(centre(0))) { - rigid_registration.set_init_translation_type (Registration::Transform::Init::set_centre_mass); + rigid_registration.set_init_translation_type(Registration::Transform::Init::set_centre_mass); } else { rigid.set_centre_without_transform_update(centre); - rigid_registration.set_init_translation_type (Registration::Transform::Init::none); + rigid_registration.set_init_translation_type(Registration::Transform::Init::none); } } - opt = get_options ("rigid_init_translation"); + opt = get_options("rigid_init_translation"); if (opt.size()) { if (init_rigid_matrix_set) - throw Exception ("options -rigid_init_matrix and -rigid_init_translation are mutually exclusive"); - Registration::set_init_translation_model_from_option (rigid_registration, (int)opt[0][0]); + throw Exception("options -rigid_init_matrix and -rigid_init_translation are mutually exclusive"); + Registration::set_init_translation_model_from_option(rigid_registration, (int)opt[0][0]); } - opt = get_options ("rigid_init_rotation"); + opt = get_options("rigid_init_rotation"); if (opt.size()) { - Registration::set_init_rotation_model_from_option (rigid_registration, (int)opt[0][0]); + Registration::set_init_rotation_model_from_option(rigid_registration, (int)opt[0][0]); } - opt = get_options ("rigid_scale"); - if (opt.size ()) { + opt = get_options("rigid_scale"); + if (opt.size()) { if (!do_rigid) - throw Exception ("the rigid multi-resolution scale factors were input when no rigid registration is requested"); - rigid_registration.set_scale_factor (parse_floats (opt[0][0])); + throw Exception("the rigid multi-resolution scale factors were input when no rigid registration is requested"); + rigid_registration.set_scale_factor(parse_floats(opt[0][0])); } - opt = get_options ("rigid_loop_density"); - if (opt.size ()) { + opt = get_options("rigid_loop_density"); + if (opt.size()) { if (!do_rigid) - throw Exception ("the rigid sparsity factor was input when no rigid registration is requested"); - rigid_registration.set_loop_density (parse_floats (opt[0][0])); + throw Exception("the rigid sparsity factor was input when no rigid registration is requested"); + rigid_registration.set_loop_density(parse_floats(opt[0][0])); } - opt = get_options ("rigid_niter"); - if (opt.size ()) { + opt = get_options("rigid_niter"); + if (opt.size()) { if (!do_rigid) - throw Exception ("the number of rigid iterations have been input when no rigid registration is requested"); - rigid_registration.set_max_iter (parse_ints (opt[0][0])); + throw Exception("the number of rigid iterations have been input when no rigid registration is requested"); + rigid_registration.set_max_iter(parse_ints(opt[0][0])); } - opt = get_options ("rigid_metric"); + opt = get_options("rigid_metric"); Registration::LinearMetricType rigid_metric = Registration::Diff; if (opt.size()) { - switch ((int)opt[0][0]){ - case 0: - rigid_metric = Registration::Diff; - break; - case 1: - rigid_metric = Registration::NCC; - break; - default: - break; + switch ((int)opt[0][0]) { + case 0: + rigid_metric = Registration::Diff; + break; + case 1: + rigid_metric = Registration::NCC; + break; + default: + break; } } if (rigid_metric == Registration::NCC) - throw Exception ("TODO: cross correlation metric not yet implemented"); + throw Exception("TODO: cross correlation metric not yet implemented"); - opt = get_options ("rigid_metric.diff.estimator"); + opt = get_options("rigid_metric.diff.estimator"); Registration::LinearRobustMetricEstimatorType rigid_estimator = Registration::None; if (opt.size()) { if (rigid_metric != Registration::Diff) - throw Exception ("rigid_metric.diff.estimator set but cost function is not diff."); + throw Exception("rigid_metric.diff.estimator set but cost function is not diff."); switch ((int)opt[0][0]) { - case 0: - rigid_estimator = Registration::L1; - break; - case 1: - rigid_estimator = Registration::L2; - break; - case 2: - rigid_estimator = Registration::LP; - break; - default: - break; + case 0: + rigid_estimator = Registration::L1; + break; + case 1: + rigid_estimator = Registration::L2; + break; + case 2: + rigid_estimator = Registration::LP; + break; + default: + break; } } - opt = get_options ("rigid_lmax"); + opt = get_options("rigid_lmax"); vector rigid_lmax; - if (opt.size ()) { + if (opt.size()) { if (!do_rigid) - throw Exception ("the -rigid_lmax option has been set when no rigid registration is requested"); + throw Exception("the -rigid_lmax option has been set when no rigid registration is requested"); if (max_mc_image_lmax == 0) - throw Exception ("-rigid_lmax option is not valid if no input image is FOD image"); - rigid_lmax = parse_ints (opt[0][0]); - for (size_t i = 0; i < rigid_lmax.size (); ++i) - if (rigid_lmax[i] > max_mc_image_lmax) { - WARN ("the requested -rigid_lmax exceeds the lmax of the input images, setting it to " + str(max_mc_image_lmax)); + throw Exception("-rigid_lmax option is not valid if no input image is FOD image"); + rigid_lmax = parse_ints(opt[0][0]); + for (size_t i = 0; i < rigid_lmax.size(); ++i) + if (rigid_lmax[i] > max_mc_image_lmax) { + WARN("the requested -rigid_lmax exceeds the lmax of the input images, setting it to " + str(max_mc_image_lmax)); rigid_lmax[i] = max_mc_image_lmax; - } - rigid_registration.set_lmax (rigid_lmax); + } + rigid_registration.set_lmax(rigid_lmax); } std::ofstream linear_logstream; - opt = get_options ("rigid_log"); + opt = get_options("rigid_log"); if (opt.size()) { if (!do_rigid) - throw Exception ("the -rigid_log option has been set when no rigid registration is requested"); - linear_logstream.open (opt[0][0]); - rigid_registration.set_log_stream (linear_logstream.rdbuf()); + throw Exception("the -rigid_log option has been set when no rigid registration is requested"); + linear_logstream.open(opt[0][0]); + rigid_registration.set_log_stream(linear_logstream.rdbuf()); } - // ****** AFFINE REGISTRATION OPTIONS ******* Registration::Linear affine_registration; - opt = get_options ("affine"); + opt = get_options("affine"); bool output_affine = false; std::string affine_filename; if (opt.size()) { - if (!do_affine) - throw Exception ("affine transformation output requested when no affine registration is requested"); - output_affine = true; - affine_filename = std::string (opt[0][0]); + if (!do_affine) + throw Exception("affine transformation output requested when no affine registration is requested"); + output_affine = true; + affine_filename = std::string(opt[0][0]); } - opt = get_options ("affine_1tomidway"); + opt = get_options("affine_1tomidway"); bool output_affine_1tomid = false; std::string affine_1tomid_filename; if (opt.size()) { - if (!do_affine) - throw Exception ("midway affine transformation output requested when no affine registration is requested"); - output_affine_1tomid = true; - affine_1tomid_filename = std::string (opt[0][0]); + if (!do_affine) + throw Exception("midway affine transformation output requested when no affine registration is requested"); + output_affine_1tomid = true; + affine_1tomid_filename = std::string(opt[0][0]); } - opt = get_options ("affine_2tomidway"); + opt = get_options("affine_2tomidway"); bool output_affine_2tomid = false; std::string affine_2tomid_filename; if (opt.size()) { - if (!do_affine) - throw Exception ("midway affine transformation output requested when no affine registration is requested"); - output_affine_2tomid = true; - affine_2tomid_filename = std::string (opt[0][0]); + if (!do_affine) + throw Exception("midway affine transformation output requested when no affine registration is requested"); + output_affine_2tomid = true; + affine_2tomid_filename = std::string(opt[0][0]); } Registration::Transform::Affine affine; - opt = get_options ("affine_init_matrix"); + opt = get_options("affine_init_matrix"); bool init_affine_matrix_set = false; if (opt.size()) { if (init_rigid_matrix_set) - throw Exception ("you cannot initialise registrations with both rigid and affine transformations"); + throw Exception("you cannot initialise registrations with both rigid and affine transformations"); if (do_rigid) - throw Exception ("you cannot initialise with -affine_init_matrix since a rigid registration is being performed"); + throw Exception("you cannot initialise with -affine_init_matrix since a rigid registration is being performed"); init_affine_matrix_set = true; Eigen::Vector3d centre; - transform_type affine_transform = File::Matrix::load_transform (opt[0][0], centre); - affine.set_transform (affine_transform); + transform_type affine_transform = File::Matrix::load_transform(opt[0][0], centre); + affine.set_transform(affine_transform); if (!std::isfinite(centre(0))) { - affine_registration.set_init_translation_type (Registration::Transform::Init::set_centre_mass); + affine_registration.set_init_translation_type(Registration::Transform::Init::set_centre_mass); } else { affine.set_centre_without_transform_update(centre); - affine_registration.set_init_translation_type (Registration::Transform::Init::none); + affine_registration.set_init_translation_type(Registration::Transform::Init::none); } } - opt = get_options ("affine_init_translation"); + opt = get_options("affine_init_translation"); if (opt.size()) { if (init_affine_matrix_set) - throw Exception ("options -affine_init_matrix and -affine_init_translation are mutually exclusive"); - Registration::set_init_translation_model_from_option (affine_registration, (int)opt[0][0]); + throw Exception("options -affine_init_matrix and -affine_init_translation are mutually exclusive"); + Registration::set_init_translation_model_from_option(affine_registration, (int)opt[0][0]); } - opt = get_options ("affine_init_rotation"); + opt = get_options("affine_init_rotation"); if (opt.size()) { if (init_affine_matrix_set) - throw Exception ("options -affine_init_matrix and -affine_init_rotation are mutually exclusive"); - Registration::set_init_rotation_model_from_option (affine_registration, (int)opt[0][0]); + throw Exception("options -affine_init_matrix and -affine_init_rotation are mutually exclusive"); + Registration::set_init_rotation_model_from_option(affine_registration, (int)opt[0][0]); } - opt = get_options ("affine_scale"); - if (opt.size ()) { + opt = get_options("affine_scale"); + if (opt.size()) { if (!do_affine) - throw Exception ("the affine multi-resolution scale factors were input when no affine registration is requested"); - affine_registration.set_scale_factor (parse_floats (opt[0][0])); + throw Exception("the affine multi-resolution scale factors were input when no affine registration is requested"); + affine_registration.set_scale_factor(parse_floats(opt[0][0])); } - opt = get_options ("affine_loop_density"); - if (opt.size ()) { + opt = get_options("affine_loop_density"); + if (opt.size()) { if (!do_affine) - throw Exception ("the affine sparsity factor was input when no affine registration is requested"); - affine_registration.set_loop_density (parse_floats (opt[0][0])); + throw Exception("the affine sparsity factor was input when no affine registration is requested"); + affine_registration.set_loop_density(parse_floats(opt[0][0])); } - opt = get_options ("affine_metric"); + opt = get_options("affine_metric"); Registration::LinearMetricType affine_metric = Registration::Diff; if (opt.size()) { - switch ((int)opt[0][0]){ - case 0: - affine_metric = Registration::Diff; - break; - case 1: - affine_metric = Registration::NCC; - break; - default: - break; + switch ((int)opt[0][0]) { + case 0: + affine_metric = Registration::Diff; + break; + case 1: + affine_metric = Registration::NCC; + break; + default: + break; } } if (affine_metric == Registration::NCC) - throw Exception ("TODO cross correlation metric not yet implemented"); + throw Exception("TODO cross correlation metric not yet implemented"); - opt = get_options ("affine_metric.diff.estimator"); + opt = get_options("affine_metric.diff.estimator"); Registration::LinearRobustMetricEstimatorType affine_estimator = Registration::None; if (opt.size()) { if (affine_metric != Registration::Diff) - throw Exception ("affine_metric.diff.estimator set but cost function is not diff."); + throw Exception("affine_metric.diff.estimator set but cost function is not diff."); switch ((int)opt[0][0]) { - case 0: - affine_estimator = Registration::L1; - break; - case 1: - affine_estimator = Registration::L2; - break; - case 2: - affine_estimator = Registration::LP; - break; - default: - break; + case 0: + affine_estimator = Registration::L1; + break; + case 1: + affine_estimator = Registration::L2; + break; + case 2: + affine_estimator = Registration::LP; + break; + default: + break; } } - opt = get_options ("affine_niter"); - if (opt.size ()) { + opt = get_options("affine_niter"); + if (opt.size()) { if (!do_affine) - throw Exception ("the number of affine iterations have been input when no affine registration is requested"); - affine_registration.set_max_iter (parse_ints (opt[0][0])); + throw Exception("the number of affine iterations have been input when no affine registration is requested"); + affine_registration.set_max_iter(parse_ints(opt[0][0])); } - opt = get_options ("affine_lmax"); + opt = get_options("affine_lmax"); vector affine_lmax; - if (opt.size ()) { + if (opt.size()) { if (!do_affine) - throw Exception ("the -affine_lmax option has been set when no affine registration is requested"); + throw Exception("the -affine_lmax option has been set when no affine registration is requested"); if (max_mc_image_lmax == 0) - throw Exception ("-affine_lmax option is not valid if no input image is FOD image"); - affine_lmax = parse_ints (opt[0][0]); - for (size_t i = 0; i < affine_lmax.size (); ++i) + throw Exception("-affine_lmax option is not valid if no input image is FOD image"); + affine_lmax = parse_ints(opt[0][0]); + for (size_t i = 0; i < affine_lmax.size(); ++i) if (affine_lmax[i] > max_mc_image_lmax) { - WARN ("the requested -affine_lmax exceeds the lmax of the input images, setting it to " + str(max_mc_image_lmax)); + WARN("the requested -affine_lmax exceeds the lmax of the input images, setting it to " + + str(max_mc_image_lmax)); affine_lmax[i] = max_mc_image_lmax; } - affine_registration.set_lmax (affine_lmax); + affine_registration.set_lmax(affine_lmax); } - opt = get_options ("affine_log"); + opt = get_options("affine_log"); if (opt.size()) { if (!do_affine) - throw Exception ("the -affine_log option has been set when no rigid registration is requested"); - linear_logstream.open (opt[0][0]); - affine_registration.set_log_stream (linear_logstream.rdbuf()); + throw Exception("the -affine_log option has been set when no rigid registration is requested"); + linear_logstream.open(opt[0][0]); + affine_registration.set_log_stream(linear_logstream.rdbuf()); } - // ****** LINEAR INITIALISATION AND STAGE OPTIONS ******* if (!do_rigid and !do_affine) { - for (auto& s: Registration::adv_init_options) { + for (auto &s : Registration::adv_init_options) { if (get_options(s.id).size()) { std::stringstream msg; msg << "cannot use option -" << s.id << " when no linear registration is requested"; - throw Exception (msg.str()); + throw Exception(msg.str()); } } - for (auto& s: Registration::lin_stage_options) { + for (auto &s : Registration::lin_stage_options) { if (get_options(s.id).size()) { std::stringstream msg; msg << "cannot use option -" << s.id << " when no linear registration is requested"; - throw Exception (msg.str()); + throw Exception(msg.str()); } } } if (do_rigid) - Registration::parse_general_options (rigid_registration); + Registration::parse_general_options(rigid_registration); if (do_affine) - Registration::parse_general_options (affine_registration); + Registration::parse_general_options(affine_registration); // ****** NON-LINEAR REGISTRATION OPTIONS ******* Registration::NonLinear nl_registration; - opt = get_options ("nl_warp"); + opt = get_options("nl_warp"); std::string warp1_filename; std::string warp2_filename; if (opt.size()) { if (!do_nonlinear) - throw Exception ("Non-linear warp output requested when no non-linear registration is requested"); - warp1_filename = std::string (opt[0][0]); - warp2_filename = std::string (opt[0][1]); + throw Exception("Non-linear warp output requested when no non-linear registration is requested"); + warp1_filename = std::string(opt[0][0]); + warp2_filename = std::string(opt[0][1]); } - opt = get_options ("nl_warp_full"); + opt = get_options("nl_warp_full"); std::string warp_full_filename; if (opt.size()) { if (!do_nonlinear) - throw Exception ("Non-linear warp output requested when no non-linear registration is requested"); - warp_full_filename = std::string (opt[0][0]); - if (!Path::is_mrtrix_image (warp_full_filename) && !(Path::has_suffix (warp_full_filename, {".nii", ".nii.gz"}) && - File::Config::get_bool ("NIfTIAutoSaveJSON", false))) - throw Exception ("nl_warp_full output requires .mif/.mih or NIfTI file format with NIfTIAutoSaveJSON config option set."); + throw Exception("Non-linear warp output requested when no non-linear registration is requested"); + warp_full_filename = std::string(opt[0][0]); + if (!Path::is_mrtrix_image(warp_full_filename) && !(Path::has_suffix(warp_full_filename, {".nii", ".nii.gz"}) && + File::Config::get_bool("NIfTIAutoSaveJSON", false))) + throw Exception( + "nl_warp_full output requires .mif/.mih or NIfTI file format with NIfTIAutoSaveJSON config option set."); } - - opt = get_options ("nl_init"); + opt = get_options("nl_init"); bool nonlinear_init = false; if (opt.size()) { nonlinear_init = true; if (!do_nonlinear) - throw Exception ("the non linear initialisation option -nl_init cannot be used when no non linear registration is requested"); + throw Exception( + "the non linear initialisation option -nl_init cannot be used when no non linear registration is requested"); - if (!Path::is_mrtrix_image (opt[0][0]) && !(Path::has_suffix (opt[0][0], {".nii", ".nii.gz"}) && - File::Config::get_bool ("NIfTIAutoLoadJSON", false) && - Path::exists(File::NIfTI::get_json_path(opt[0][0])))) - WARN ("nl_init input requires warp_full in original .mif/.mih file format or in NIfTI file format with associated JSON. " - "Converting to other file formats may remove linear transformations stored in the image header."); + if (!Path::is_mrtrix_image(opt[0][0]) && + !(Path::has_suffix(opt[0][0], {".nii", ".nii.gz"}) && File::Config::get_bool("NIfTIAutoLoadJSON", false) && + Path::exists(File::NIfTI::get_json_path(opt[0][0])))) + WARN("nl_init input requires warp_full in original .mif/.mih file format or in NIfTI file format with associated " + "JSON. " + "Converting to other file formats may remove linear transformations stored in the image header."); - Image input_warps = Image::open (opt[0][0]); + Image input_warps = Image::open(opt[0][0]); if (input_warps.ndim() != 5) - throw Exception ("non-linear initialisation input is not 5D. Input must be from previous non-linear output"); + throw Exception("non-linear initialisation input is not 5D. Input must be from previous non-linear output"); - nl_registration.initialise (input_warps); + nl_registration.initialise(input_warps); if (do_affine) { - WARN ("no affine registration will be performed when initialising with non-linear non-linear warps"); + WARN("no affine registration will be performed when initialising with non-linear non-linear warps"); do_affine = false; } if (do_rigid) { - WARN ("no rigid registration will be performed when initialising with non-linear non-linear warps"); + WARN("no rigid registration will be performed when initialising with non-linear non-linear warps"); do_rigid = false; } if (init_affine_matrix_set) - WARN ("-affine_init has no effect since the non-linear init warp also contains the linear transform in the image header"); + WARN("-affine_init has no effect since the non-linear init warp also contains the linear transform in the image " + "header"); if (init_rigid_matrix_set) - WARN ("-rigid_init has no effect since the non-linear init warp also contains the linear transform in the image header"); + WARN("-rigid_init has no effect since the non-linear init warp also contains the linear transform in the image " + "header"); } - - opt = get_options ("nl_scale"); - if (opt.size ()) { + opt = get_options("nl_scale"); + if (opt.size()) { if (!do_nonlinear) - throw Exception ("the non-linear multi-resolution scale factors were input when no non-linear registration is requested"); - vector scale_factors = parse_floats (opt[0][0]); + throw Exception( + "the non-linear multi-resolution scale factors were input when no non-linear registration is requested"); + vector scale_factors = parse_floats(opt[0][0]); if (nonlinear_init) { - WARN ("-nl_scale option ignored since only the full resolution will be performed when initialising with non-linear warp"); + WARN("-nl_scale option ignored since only the full resolution will be performed when initialising with " + "non-linear warp"); } else { - nl_registration.set_scale_factor (scale_factors); + nl_registration.set_scale_factor(scale_factors); } } - opt = get_options ("nl_niter"); - if (opt.size ()) { + opt = get_options("nl_niter"); + if (opt.size()) { if (!do_nonlinear) - throw Exception ("the number of non-linear iterations have been input when no non-linear registration is requested"); - vector iterations_per_level = parse_ints (opt[0][0]); + throw Exception( + "the number of non-linear iterations have been input when no non-linear registration is requested"); + vector iterations_per_level = parse_ints(opt[0][0]); if (nonlinear_init && iterations_per_level.size() > 1) - throw Exception ("when initialising the non-linear registration the max number of iterations can only be defined for a single level"); + throw Exception("when initialising the non-linear registration the max number of iterations can only be defined " + "for a single level"); else - nl_registration.set_max_iter (iterations_per_level); + nl_registration.set_max_iter(iterations_per_level); } - opt = get_options ("nl_update_smooth"); + opt = get_options("nl_update_smooth"); if (opt.size()) { if (!do_nonlinear) - throw Exception ("the warp update field smoothing parameter was input when no non-linear registration is requested"); - nl_registration.set_update_smoothing (opt[0][0]); + throw Exception( + "the warp update field smoothing parameter was input when no non-linear registration is requested"); + nl_registration.set_update_smoothing(opt[0][0]); } - opt = get_options ("nl_disp_smooth"); + opt = get_options("nl_disp_smooth"); if (opt.size()) { if (!do_nonlinear) - throw Exception ("the displacement field smoothing parameter was input when no non-linear registration is requested"); - nl_registration.set_disp_smoothing (opt[0][0]); + throw Exception( + "the displacement field smoothing parameter was input when no non-linear registration is requested"); + nl_registration.set_disp_smoothing(opt[0][0]); } - opt = get_options ("nl_grad_step"); + opt = get_options("nl_grad_step"); if (opt.size()) { if (!do_nonlinear) - throw Exception ("the initial gradient step size was input when no non-linear registration is requested"); - nl_registration.set_init_grad_step (opt[0][0]); + throw Exception("the initial gradient step size was input when no non-linear registration is requested"); + nl_registration.set_init_grad_step(opt[0][0]); } - opt = get_options ("nl_lmax"); + opt = get_options("nl_lmax"); vector nl_lmax; if (opt.size()) { if (!do_nonlinear) - throw Exception ("the -nl_lmax option has been set when no non-linear registration is requested"); + throw Exception("the -nl_lmax option has been set when no non-linear registration is requested"); if (max_mc_image_lmax == 0) - throw Exception ("-nl_lmax option is not valid if no input image is FOD image"); - nl_lmax = parse_ints (opt[0][0]); - nl_registration.set_lmax (nl_lmax); - for (size_t i = 0; i < (nl_lmax).size (); ++i) + throw Exception("-nl_lmax option is not valid if no input image is FOD image"); + nl_lmax = parse_ints(opt[0][0]); + nl_registration.set_lmax(nl_lmax); + for (size_t i = 0; i < (nl_lmax).size(); ++i) if ((nl_lmax)[i] > max_mc_image_lmax) - throw Exception ("the requested -nl_lmax exceeds the lmax of the input images"); + throw Exception("the requested -nl_lmax exceeds the lmax of the input images"); } - // ****** MC options ******* // TODO: set tissue specific lmax? - opt = get_options ("mc_weights"); + opt = get_options("mc_weights"); if (opt.size()) { - vector mc_weights = parse_floats (opt[0][0]); + vector mc_weights = parse_floats(opt[0][0]); if (mc_weights.size() == 1) - mc_weights.resize (n_images, mc_weights[0]); + mc_weights.resize(n_images, mc_weights[0]); else if (mc_weights.size() != n_images) - throw Exception ("number of mc_weights does not match number of contrasts"); - for (const default_type & w : mc_weights) - if (w < 0.0) throw Exception ("mc_weights must be non-negative"); + throw Exception("number of mc_weights does not match number of contrasts"); + for (const default_type &w : mc_weights) + if (w < 0.0) + throw Exception("mc_weights must be non-negative"); if (do_nonlinear) { default_type sm = 0.0; - std::for_each (mc_weights.begin(), mc_weights.end(), [&] (default_type n) {sm += n;}); - if (MR::abs (sm - n_images) > 1.e-6) - WARN ("mc_weights do not sum to the number of contrasts. This changes the regularisation of the nonlinear registration."); + std::for_each(mc_weights.begin(), mc_weights.end(), [&](default_type n) { sm += n; }); + if (MR::abs(sm - n_images) > 1.e-6) + WARN("mc_weights do not sum to the number of contrasts. This changes the regularisation of the nonlinear " + "registration."); } for (size_t idx = 0; idx < n_images; idx++) @@ -795,33 +834,39 @@ void run () { { ssize_t max_requested_lmax = 0; if (max_mc_image_lmax != 0) { - if (do_rigid) max_requested_lmax = std::max(max_requested_lmax, rigid_registration.get_lmax()); - if (do_affine) max_requested_lmax = std::max(max_requested_lmax, affine_registration.get_lmax()); - if (do_nonlinear) max_requested_lmax = std::max(max_requested_lmax, nl_registration.get_lmax()); - INFO ("maximum used lmax: "+str(max_requested_lmax)); + if (do_rigid) + max_requested_lmax = std::max(max_requested_lmax, rigid_registration.get_lmax()); + if (do_affine) + max_requested_lmax = std::max(max_requested_lmax, affine_registration.get_lmax()); + if (do_nonlinear) + max_requested_lmax = std::max(max_requested_lmax, nl_registration.get_lmax()); + INFO("maximum used lmax: " + str(max_requested_lmax)); } for (size_t idx = 0; idx < n_images; ++idx) { - mc_params[idx].lmax = std::min (mc_params[idx].image_lmax, max_requested_lmax); + mc_params[idx].lmax = std::min(mc_params[idx].image_lmax, max_requested_lmax); if (input1[idx].ndim() == 3) mc_params[idx].nvols = 1; else if (mc_params[idx].do_reorientation) { - mc_params[idx].nvols = Math::SH::NforL (mc_params[idx].lmax); + mc_params[idx].nvols = Math::SH::NforL(mc_params[idx].lmax); } else mc_params[idx].nvols = input1[idx].size(3); } mc_params[0].start = 0; for (size_t idx = 1; idx < n_images; ++idx) - mc_params[idx].start = mc_params[idx-1].start + mc_params[idx-1].nvols; + mc_params[idx].start = mc_params[idx - 1].start + mc_params[idx - 1].nvols; - for (const auto & mc : mc_params) - DEBUG (str(mc)); + for (const auto &mc : mc_params) + DEBUG(str(mc)); } if (mc_params.size() > 1) { - if (do_rigid) rigid_registration.set_mc_parameters (mc_params); - if (do_affine) affine_registration.set_mc_parameters (mc_params); - if (do_nonlinear) nl_registration.set_mc_parameters (mc_params); + if (do_rigid) + rigid_registration.set_mc_parameters(mc_params); + if (do_affine) + affine_registration.set_mc_parameters(mc_params); + if (do_nonlinear) + nl_registration.set_mc_parameters(mc_params); } // ****** PARSING DONE, PRELOAD THE DATA ******* @@ -829,346 +874,396 @@ void run () { // load multiple tissue types into the same 4D image // drop last axis if input is 4D with one volume for speed reasons Image images1, images2; - INFO ("preloading input1..."); - Registration::preload_data (input1, images1, mc_params); - INFO ("preloading input2..."); - Registration::preload_data (input2, images2, mc_params); - INFO ("preloading input images done"); + INFO("preloading input1..."); + Registration::preload_data(input1, images1, mc_params); + INFO("preloading input2..."); + Registration::preload_data(input2, images2, mc_params); + INFO("preloading input images done"); // ****** RUN RIGID REGISTRATION ******* if (do_rigid) { - CONSOLE ("running rigid registration"); + CONSOLE("running rigid registration"); if (images2.ndim() == 4) { if (do_reorientation) - rigid_registration.set_directions (directions_cartesian); + rigid_registration.set_directions(directions_cartesian); // if (rigid_metric == Registration::NCC) // TODO if (rigid_metric == Registration::Diff) { if (rigid_estimator == Registration::None) { Registration::Metric::MeanSquared4D, Image> metric; - rigid_registration.run_masked (metric, rigid, images1, images2, im1_mask, im2_mask); + rigid_registration.run_masked(metric, rigid, images1, images2, im1_mask, im2_mask); } else if (rigid_estimator == Registration::L1) { Registration::Metric::L1 estimator; - Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::L1> metric (images1, images2, estimator); - rigid_registration.run_masked (metric, rigid, images1, images2, im1_mask, im2_mask); + Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::L1> + metric(images1, images2, estimator); + rigid_registration.run_masked(metric, rigid, images1, images2, im1_mask, im2_mask); } else if (rigid_estimator == Registration::L2) { Registration::Metric::L2 estimator; - Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::L2> metric (images1, images2, estimator); - rigid_registration.run_masked (metric, rigid, images1, images2, im1_mask, im2_mask); + Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::L2> + metric(images1, images2, estimator); + rigid_registration.run_masked(metric, rigid, images1, images2, im1_mask, im2_mask); } else if (rigid_estimator == Registration::LP) { Registration::Metric::LP estimator; - Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::LP> metric (images1, images2, estimator); - rigid_registration.run_masked (metric, rigid, images1, images2, im1_mask, im2_mask); - } else throw Exception ("FIXME: estimator selection"); - } else throw Exception ("FIXME: metric selection"); + Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::LP> + metric(images1, images2, estimator); + rigid_registration.run_masked(metric, rigid, images1, images2, im1_mask, im2_mask); + } else + throw Exception("FIXME: estimator selection"); + } else + throw Exception("FIXME: metric selection"); } else { // 3D - if (rigid_metric == Registration::NCC){ + if (rigid_metric == Registration::NCC) { Registration::Metric::LocalCrossCorrelation metric; - vector extent(3,3); - rigid_registration.set_extent (extent); - rigid_registration.run_masked (metric, rigid, images1, images2, im1_mask, im2_mask); - } - else if (rigid_metric == Registration::Diff) { + vector extent(3, 3); + rigid_registration.set_extent(extent); + rigid_registration.run_masked(metric, rigid, images1, images2, im1_mask, im2_mask); + } else if (rigid_metric == Registration::Diff) { if (rigid_estimator == Registration::None) { Registration::Metric::MeanSquared metric; - rigid_registration.run_masked (metric, rigid, images1, images2, im1_mask, im2_mask); + rigid_registration.run_masked(metric, rigid, images1, images2, im1_mask, im2_mask); } else if (rigid_estimator == Registration::L1) { Registration::Metric::L1 estimator; Registration::Metric::DifferenceRobust metric(estimator); - rigid_registration.run_masked (metric, rigid, images1, images2, im1_mask, im2_mask); + rigid_registration.run_masked(metric, rigid, images1, images2, im1_mask, im2_mask); } else if (rigid_estimator == Registration::L2) { Registration::Metric::L2 estimator; Registration::Metric::DifferenceRobust metric(estimator); - rigid_registration.run_masked (metric, rigid, images1, images2, im1_mask, im2_mask); + rigid_registration.run_masked(metric, rigid, images1, images2, im1_mask, im2_mask); } else if (rigid_estimator == Registration::LP) { Registration::Metric::LP estimator; Registration::Metric::DifferenceRobust metric(estimator); - rigid_registration.run_masked (metric, rigid, images1, images2, im1_mask, im2_mask); - } else throw Exception ("FIXME: estimator selection"); - } else throw Exception ("FIXME: metric selection"); + rigid_registration.run_masked(metric, rigid, images1, images2, im1_mask, im2_mask); + } else + throw Exception("FIXME: estimator selection"); + } else + throw Exception("FIXME: metric selection"); } if (output_rigid_1tomid) - File::Matrix::save_transform (rigid.get_transform_half(), rigid.get_centre(), rigid_1tomid_filename); + File::Matrix::save_transform(rigid.get_transform_half(), rigid.get_centre(), rigid_1tomid_filename); if (output_rigid_2tomid) - File::Matrix::save_transform (rigid.get_transform_half_inverse(), rigid.get_centre(), rigid_2tomid_filename); + File::Matrix::save_transform(rigid.get_transform_half_inverse(), rigid.get_centre(), rigid_2tomid_filename); if (output_rigid) - File::Matrix::save_transform (rigid.get_transform(), rigid.get_centre(), rigid_filename); + File::Matrix::save_transform(rigid.get_transform(), rigid.get_centre(), rigid_filename); } // ****** RUN AFFINE REGISTRATION ******* if (do_affine) { - CONSOLE ("running affine registration"); + CONSOLE("running affine registration"); if (do_rigid) { - affine.set_centre (rigid.get_centre()); - affine.set_transform (rigid.get_transform()); - affine_registration.set_init_translation_type (Registration::Transform::Init::none); + affine.set_centre(rigid.get_centre()); + affine.set_transform(rigid.get_transform()); + affine_registration.set_init_translation_type(Registration::Transform::Init::none); } if (images2.ndim() == 4) { if (do_reorientation) - affine_registration.set_directions (directions_cartesian); + affine_registration.set_directions(directions_cartesian); // if (affine_metric == Registration::NCC) // TODO if (affine_metric == Registration::Diff) { if (affine_estimator == Registration::None) { Registration::Metric::MeanSquared4D, Image> metric; - affine_registration.run_masked (metric, affine, images1, images2, im1_mask, im2_mask); + affine_registration.run_masked(metric, affine, images1, images2, im1_mask, im2_mask); } else if (affine_estimator == Registration::L1) { Registration::Metric::L1 estimator; - Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::L1> metric (images1, images2, estimator); - affine_registration.run_masked (metric, affine, images1, images2, im1_mask, im2_mask); + Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::L1> + metric(images1, images2, estimator); + affine_registration.run_masked(metric, affine, images1, images2, im1_mask, im2_mask); } else if (affine_estimator == Registration::L2) { Registration::Metric::L2 estimator; - Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::L2> metric (images1, images2, estimator); - affine_registration.run_masked (metric, affine, images1, images2, im1_mask, im2_mask); + Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::L2> + metric(images1, images2, estimator); + affine_registration.run_masked(metric, affine, images1, images2, im1_mask, im2_mask); } else if (affine_estimator == Registration::LP) { Registration::Metric::LP estimator; - Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::LP> metric (images1, images2, estimator); - affine_registration.run_masked (metric, affine, images1, images2, im1_mask, im2_mask); - } else throw Exception ("FIXME: estimator selection"); - } else throw Exception ("FIXME: metric selection"); + Registration::Metric::DifferenceRobust4D, Image, Registration::Metric::LP> + metric(images1, images2, estimator); + affine_registration.run_masked(metric, affine, images1, images2, im1_mask, im2_mask); + } else + throw Exception("FIXME: estimator selection"); + } else + throw Exception("FIXME: metric selection"); } else { // 3D - if (affine_metric == Registration::NCC){ + if (affine_metric == Registration::NCC) { Registration::Metric::LocalCrossCorrelation metric; - vector extent(3,3); - affine_registration.set_extent (extent); - affine_registration.run_masked (metric, affine, images1, images2, im1_mask, im2_mask); - } - else if (affine_metric == Registration::Diff) { + vector extent(3, 3); + affine_registration.set_extent(extent); + affine_registration.run_masked(metric, affine, images1, images2, im1_mask, im2_mask); + } else if (affine_metric == Registration::Diff) { if (affine_estimator == Registration::None) { Registration::Metric::MeanSquared metric; - affine_registration.run_masked (metric, affine, images1, images2, im1_mask, im2_mask); + affine_registration.run_masked(metric, affine, images1, images2, im1_mask, im2_mask); } else if (affine_estimator == Registration::L1) { Registration::Metric::L1 estimator; Registration::Metric::DifferenceRobust metric(estimator); - affine_registration.run_masked (metric, affine, images1, images2, im1_mask, im2_mask); + affine_registration.run_masked(metric, affine, images1, images2, im1_mask, im2_mask); } else if (affine_estimator == Registration::L2) { Registration::Metric::L2 estimator; Registration::Metric::DifferenceRobust metric(estimator); - affine_registration.run_masked (metric, affine, images1, images2, im1_mask, im2_mask); + affine_registration.run_masked(metric, affine, images1, images2, im1_mask, im2_mask); } else if (affine_estimator == Registration::LP) { Registration::Metric::LP estimator; Registration::Metric::DifferenceRobust metric(estimator); - affine_registration.run_masked (metric, affine, images1, images2, im1_mask, im2_mask); - } else throw Exception ("FIXME: estimator selection"); - } else throw Exception ("FIXME: metric selection"); + affine_registration.run_masked(metric, affine, images1, images2, im1_mask, im2_mask); + } else + throw Exception("FIXME: estimator selection"); + } else + throw Exception("FIXME: metric selection"); } if (output_affine_1tomid) - File::Matrix::save_transform (affine.get_transform_half(), affine.get_centre(), affine_1tomid_filename); + File::Matrix::save_transform(affine.get_transform_half(), affine.get_centre(), affine_1tomid_filename); if (output_affine_2tomid) - File::Matrix::save_transform (affine.get_transform_half_inverse(), affine.get_centre(), affine_2tomid_filename); + File::Matrix::save_transform(affine.get_transform_half_inverse(), affine.get_centre(), affine_2tomid_filename); if (output_affine) - File::Matrix::save_transform (affine.get_transform(), affine.get_centre(), affine_filename); + File::Matrix::save_transform(affine.get_transform(), affine.get_centre(), affine_filename); } - // ****** RUN NON-LINEAR REGISTRATION ******* if (do_nonlinear) { - CONSOLE ("running non-linear registration"); + CONSOLE("running non-linear registration"); if (do_reorientation) - nl_registration.set_aPSF_directions (directions_cartesian); + nl_registration.set_aPSF_directions(directions_cartesian); if (do_affine || init_affine_matrix_set) { - nl_registration.run (affine, images1, images2, im1_mask, im2_mask); + nl_registration.run(affine, images1, images2, im1_mask, im2_mask); } else if (do_rigid || init_rigid_matrix_set) { - nl_registration.run (rigid, images1, images2, im1_mask, im2_mask); + nl_registration.run(rigid, images1, images2, im1_mask, im2_mask); } else { Registration::Transform::Affine identity_transform; - nl_registration.run (identity_transform, images1, images2, im1_mask, im2_mask); + nl_registration.run(identity_transform, images1, images2, im1_mask, im2_mask); } if (warp_full_filename.size()) { - //TODO add affine parameters to comments too? + // TODO add affine parameters to comments too? Header output_header = nl_registration.get_output_warps_header(); - nl_registration.write_params_to_header (output_header); - nl_registration.write_linear_to_header (output_header); - output_header.datatype() = DataType::from_command_line (DataType::Float32); - auto output_warps = Image::create (warp_full_filename, output_header); - nl_registration.get_output_warps (output_warps); + nl_registration.write_params_to_header(output_header); + nl_registration.write_linear_to_header(output_header); + output_header.datatype() = DataType::from_command_line(DataType::Float32); + auto output_warps = Image::create(warp_full_filename, output_header); + nl_registration.get_output_warps(output_warps); } if (warp1_filename.size()) { - Header output_header (images2); + Header output_header(images2); output_header.ndim() = 4; - output_header.size(3) =3; - nl_registration.write_params_to_header (output_header); - output_header.datatype() = DataType::from_command_line (DataType::Float32); - auto warp1 = Image::create (warp1_filename, output_header).with_direct_io(); - Registration::Warp::compute_full_deformation (nl_registration.get_im2_to_mid_linear().inverse(), - *(nl_registration.get_mid_to_im2()), - *(nl_registration.get_im1_to_mid()), - nl_registration.get_im1_to_mid_linear(), warp1); + output_header.size(3) = 3; + nl_registration.write_params_to_header(output_header); + output_header.datatype() = DataType::from_command_line(DataType::Float32); + auto warp1 = Image::create(warp1_filename, output_header).with_direct_io(); + Registration::Warp::compute_full_deformation(nl_registration.get_im2_to_mid_linear().inverse(), + *(nl_registration.get_mid_to_im2()), + *(nl_registration.get_im1_to_mid()), + nl_registration.get_im1_to_mid_linear(), + warp1); } if (warp2_filename.size()) { - Header output_header (images1); + Header output_header(images1); output_header.ndim() = 4; output_header.size(3) = 3; - nl_registration.write_params_to_header (output_header); - output_header.datatype() = DataType::from_command_line (DataType::Float32); - auto warp2 = Image::create (warp2_filename, output_header).with_direct_io(); - Registration::Warp::compute_full_deformation (nl_registration.get_im1_to_mid_linear().inverse(), - *(nl_registration.get_mid_to_im1()), - *(nl_registration.get_im2_to_mid()), - nl_registration.get_im2_to_mid_linear(), warp2); + nl_registration.write_params_to_header(output_header); + output_header.datatype() = DataType::from_command_line(DataType::Float32); + auto warp2 = Image::create(warp2_filename, output_header).with_direct_io(); + Registration::Warp::compute_full_deformation(nl_registration.get_im1_to_mid_linear().inverse(), + *(nl_registration.get_mid_to_im1()), + *(nl_registration.get_im2_to_mid()), + nl_registration.get_im2_to_mid_linear(), + warp2); } } - if (im1_transformed_paths.size()) { - CONSOLE ("Writing input images1 transformed to space of images2..."); + CONSOLE("Writing input images1 transformed to space of images2..."); Image deform_field; if (do_nonlinear) { - Header deform_header (input2[0]); + Header deform_header(input2[0]); deform_header.ndim() = 4; deform_header.size(3) = 3; - deform_field = Image::scratch (deform_header); - Registration::Warp::compute_full_deformation (nl_registration.get_im2_to_mid_linear().inverse(), - *(nl_registration.get_mid_to_im2()), - *(nl_registration.get_im1_to_mid()), - nl_registration.get_im1_to_mid_linear(), - deform_field); + deform_field = Image::scratch(deform_header); + Registration::Warp::compute_full_deformation(nl_registration.get_im2_to_mid_linear().inverse(), + *(nl_registration.get_mid_to_im2()), + *(nl_registration.get_im1_to_mid()), + nl_registration.get_im1_to_mid_linear(), + deform_field); } for (size_t idx = 0; idx < im1_transformed_paths.size(); idx++) { - CONSOLE ("... " + im1_transformed_paths[idx]); + CONSOLE("... " + im1_transformed_paths[idx]); { // LogLevelLatch log_level (0); - Image im1_image = Image::open (input1[idx].name()); + Image im1_image = Image::open(input1[idx].name()); - Header transformed_header (input2[idx]); - transformed_header.datatype() = DataType::from_command_line (DataType::Float32); - Image im1_transformed = Image::create (im1_transformed_paths[idx], transformed_header); + Header transformed_header(input2[idx]); + transformed_header.datatype() = DataType::from_command_line(DataType::Float32); + Image im1_transformed = Image::create(im1_transformed_paths[idx], transformed_header); const size_t nvols = im1_image.ndim() == 3 ? 1 : im1_image.size(3); - const bool reorient_output = !reorientation_forbidden && (nvols > 1) && SH::NforL(SH::LforN(nvols)) == nvols; + const bool reorient_output = !reorientation_forbidden && (nvols > 1) && SH::NforL(SH::LforN(nvols)) == nvols; if (do_nonlinear) { - Filter::warp (im1_image, im1_transformed, deform_field, out_of_bounds_value); + Filter::warp(im1_image, im1_transformed, deform_field, out_of_bounds_value); if (reorient_output) - Registration::Transform::reorient_warp ("reorienting FODs", - im1_transformed, - deform_field, - Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Registration::Transform::reorient_warp( + "reorienting FODs", + im1_transformed, + deform_field, + Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()).transpose()); } else if (do_affine) { - Filter::reslice (im1_image, im1_transformed, affine.get_transform(), Adapter::AutoOverSample, out_of_bounds_value); + Filter::reslice( + im1_image, im1_transformed, affine.get_transform(), Adapter::AutoOverSample, out_of_bounds_value); if (reorient_output) - Registration::Transform::reorient ("reorienting FODs", - im1_transformed, - im1_transformed, - affine.get_transform(), - Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Registration::Transform::reorient( + "reorienting FODs", + im1_transformed, + im1_transformed, + affine.get_transform(), + Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()).transpose()); } else { // rigid - Filter::reslice (im1_image, im1_transformed, rigid.get_transform(), Adapter::AutoOverSample, out_of_bounds_value); + Filter::reslice( + im1_image, im1_transformed, rigid.get_transform(), Adapter::AutoOverSample, out_of_bounds_value); if (reorient_output) - Registration::Transform::reorient ("reorienting FODs", - im1_transformed, - im1_transformed, - rigid.get_transform(), - Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Registration::Transform::reorient( + "reorienting FODs", + im1_transformed, + im1_transformed, + rigid.get_transform(), + Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()).transpose()); } } } } - if (input1_midway_transformed_paths.size() and input2_midway_transformed_paths.size()) { Header midway_header; Image im1_deform_field, im2_deform_field; if (do_nonlinear) - midway_header = Header (*nl_registration.get_im1_to_mid()); + midway_header = Header(*nl_registration.get_im1_to_mid()); else if (do_affine) - midway_header = compute_minimum_average_header (input1[0], input2[0], affine.get_transform_half_inverse(), affine.get_transform_half()); + midway_header = compute_minimum_average_header( + input1[0], input2[0], affine.get_transform_half_inverse(), affine.get_transform_half()); else // rigid - midway_header = compute_minimum_average_header (input1[0], input2[0], rigid.get_transform_half_inverse(), rigid.get_transform_half()); - midway_header.datatype() = DataType::from_command_line (DataType::Float32); + midway_header = compute_minimum_average_header( + input1[0], input2[0], rigid.get_transform_half_inverse(), rigid.get_transform_half()); + midway_header.datatype() = DataType::from_command_line(DataType::Float32); // process input1 then input2 to reduce memory consumption - CONSOLE ("Writing input1 transformed to midway..."); + CONSOLE("Writing input1 transformed to midway..."); if (do_nonlinear) { - im1_deform_field = Image::scratch (*(nl_registration.get_im1_to_mid())); - Registration::Warp::compose_linear_deformation (nl_registration.get_im1_to_mid_linear(), *(nl_registration.get_im1_to_mid()), im1_deform_field); + im1_deform_field = Image::scratch(*(nl_registration.get_im1_to_mid())); + Registration::Warp::compose_linear_deformation( + nl_registration.get_im1_to_mid_linear(), *(nl_registration.get_im1_to_mid()), im1_deform_field); } for (size_t idx = 0; idx < input1_midway_transformed_paths.size(); idx++) { - CONSOLE ("... " + input1_midway_transformed_paths[idx]); + CONSOLE("... " + input1_midway_transformed_paths[idx]); { // LogLevelLatch log_level (0); - Image im1_image = Image::open (input1[idx].name()); + Image im1_image = Image::open(input1[idx].name()); midway_header.ndim() = im1_image.ndim(); if (midway_header.ndim() == 4) midway_header.size(3) = im1_image.size(3); const size_t nvols = im1_image.ndim() == 3 ? 1 : im1_image.size(3); - const bool reorient_output = !reorientation_forbidden && (nvols > 1) && SH::NforL(SH::LforN(nvols)) == nvols; + const bool reorient_output = !reorientation_forbidden && (nvols > 1) && SH::NforL(SH::LforN(nvols)) == nvols; if (do_nonlinear) { - auto im1_midway = Image::create (input1_midway_transformed_paths[idx], midway_header); - Filter::warp (im1_image, im1_midway, im1_deform_field, out_of_bounds_value); + auto im1_midway = Image::create(input1_midway_transformed_paths[idx], midway_header); + Filter::warp(im1_image, im1_midway, im1_deform_field, out_of_bounds_value); if (reorient_output) - Registration::Transform::reorient_warp ("reorienting ODFs", im1_midway, im1_deform_field, - Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Registration::Transform::reorient_warp( + "reorienting ODFs", + im1_midway, + im1_deform_field, + Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()).transpose()); } else if (do_affine) { - auto im1_midway = Image::create (input1_midway_transformed_paths[idx], midway_header); - Filter::reslice (im1_image, im1_midway, affine.get_transform_half(), Adapter::AutoOverSample, out_of_bounds_value); + auto im1_midway = Image::create(input1_midway_transformed_paths[idx], midway_header); + Filter::reslice( + im1_image, im1_midway, affine.get_transform_half(), Adapter::AutoOverSample, out_of_bounds_value); if (reorient_output) - Registration::Transform::reorient ("reorienting ODFs", im1_midway, im1_midway, affine.get_transform_half(), Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Registration::Transform::reorient( + "reorienting ODFs", + im1_midway, + im1_midway, + affine.get_transform_half(), + Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()).transpose()); } else { // rigid - auto im1_midway = Image::create (input1_midway_transformed_paths[idx], midway_header); - Filter::reslice (im1_image, im1_midway, rigid.get_transform_half(), Adapter::AutoOverSample, out_of_bounds_value); + auto im1_midway = Image::create(input1_midway_transformed_paths[idx], midway_header); + Filter::reslice( + im1_image, im1_midway, rigid.get_transform_half(), Adapter::AutoOverSample, out_of_bounds_value); if (reorient_output) - Registration::Transform::reorient ("reorienting ODFs", im1_midway, im1_midway, rigid.get_transform_half(), Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Registration::Transform::reorient( + "reorienting ODFs", + im1_midway, + im1_midway, + rigid.get_transform_half(), + Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()).transpose()); } } } - CONSOLE ("Writing input2 transformed to midway..."); + CONSOLE("Writing input2 transformed to midway..."); if (do_nonlinear) { - im2_deform_field = Image::scratch (*(nl_registration.get_im2_to_mid())); - Registration::Warp::compose_linear_deformation (nl_registration.get_im2_to_mid_linear(), *(nl_registration.get_im2_to_mid()), im2_deform_field); + im2_deform_field = Image::scratch(*(nl_registration.get_im2_to_mid())); + Registration::Warp::compose_linear_deformation( + nl_registration.get_im2_to_mid_linear(), *(nl_registration.get_im2_to_mid()), im2_deform_field); } for (size_t idx = 0; idx < input2_midway_transformed_paths.size(); idx++) { - CONSOLE ("... " + input2_midway_transformed_paths[idx]); + CONSOLE("... " + input2_midway_transformed_paths[idx]); { // LogLevelLatch log_level (0); - Image im2_image = Image::open (input2[idx].name()); + Image im2_image = Image::open(input2[idx].name()); midway_header.ndim() = im2_image.ndim(); if (midway_header.ndim() == 4) midway_header.size(3) = im2_image.size(3); const size_t nvols = im2_image.ndim() == 3 ? 1 : im2_image.size(3); - const value_type val = (std::sqrt (float (1 + 8 * nvols)) - 3.0) / 4.0; - const bool reorient_output = !reorientation_forbidden && (nvols > 1) && !(val - (int)val); + const value_type val = (std::sqrt(float(1 + 8 * nvols)) - 3.0) / 4.0; + const bool reorient_output = !reorientation_forbidden && (nvols > 1) && !(val - (int)val); if (do_nonlinear) { - auto im2_midway = Image::create (input2_midway_transformed_paths[idx], midway_header); - Filter::warp (im2_image, im2_midway, im2_deform_field, out_of_bounds_value); + auto im2_midway = Image::create(input2_midway_transformed_paths[idx], midway_header); + Filter::warp(im2_image, im2_midway, im2_deform_field, out_of_bounds_value); if (reorient_output) - Registration::Transform::reorient_warp ("reorienting ODFs", im2_midway, im2_deform_field, - Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Registration::Transform::reorient_warp( + "reorienting ODFs", + im2_midway, + im2_deform_field, + Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()).transpose()); } else if (do_affine) { - auto im2_midway = Image::create (input2_midway_transformed_paths[idx], midway_header); - Filter::reslice (im2_image, im2_midway, affine.get_transform_half_inverse(), Adapter::AutoOverSample, out_of_bounds_value); + auto im2_midway = Image::create(input2_midway_transformed_paths[idx], midway_header); + Filter::reslice( + im2_image, im2_midway, affine.get_transform_half_inverse(), Adapter::AutoOverSample, out_of_bounds_value); if (reorient_output) - Registration::Transform::reorient ("reorienting ODFs", im2_midway, im2_midway, affine.get_transform_half_inverse(), Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Registration::Transform::reorient( + "reorienting ODFs", + im2_midway, + im2_midway, + affine.get_transform_half_inverse(), + Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()).transpose()); } else { // rigid - auto im2_midway = Image::create (input2_midway_transformed_paths[idx], midway_header); - Filter::reslice (im2_image, im2_midway, rigid.get_transform_half_inverse(), Adapter::AutoOverSample, out_of_bounds_value); + auto im2_midway = Image::create(input2_midway_transformed_paths[idx], midway_header); + Filter::reslice( + im2_image, im2_midway, rigid.get_transform_half_inverse(), Adapter::AutoOverSample, out_of_bounds_value); if (reorient_output) - Registration::Transform::reorient ("reorienting ODFs", im2_midway, im2_midway, rigid.get_transform_half_inverse(), Math::Sphere::spherical2cartesian (DWI::Directions::electrostatic_repulsion_300()).transpose()); + Registration::Transform::reorient( + "reorienting ODFs", + im2_midway, + im2_midway, + rigid.get_transform_half_inverse(), + Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()).transpose()); } } } } - if (get_options ("affine_log").size() or get_options ("rigid_log").size()) + if (get_options("affine_log").size() or get_options("rigid_log").size()) linear_logstream.close(); } diff --git a/cmd/mrstats.cpp b/cmd/mrstats.cpp index 19b9420546..92aabe5403 100644 --- a/cmd/mrstats.cpp +++ b/cmd/mrstats.cpp @@ -28,129 +28,105 @@ #include "algo/loop.h" #include "file/ofstream.h" - - using namespace MR; using namespace App; +void usage() { + AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; -void usage () -{ -AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; - -SYNOPSIS = "Compute images statistics"; + SYNOPSIS = "Compute images statistics"; -ARGUMENTS - + Argument ("image", - "the input image from which statistics will be computed.") - .type_image_in (); + ARGUMENTS + +Argument("image", "the input image from which statistics will be computed.").type_image_in(); -OPTIONS - + Stats::Options - - + OptionGroup ("Additional options for mrstats") - + Option ("allvolumes", "generate statistics across all image volumes, rather than one set of statistics per image volume"); + OPTIONS + +Stats::Options + + OptionGroup("Additional options for mrstats") + + Option("allvolumes", + "generate statistics across all image volumes, rather than one set of statistics per image volume"); } - using value_type = Stats::value_type; using complex_type = Stats::complex_type; +class Volume_loop { +public: + Volume_loop(Image &in) : image(in), is_4D(in.ndim() == 4), status(true) { + if (is_4D) + image.index(3) = 0; + } -class Volume_loop -{ - public: - Volume_loop (Image& in) : - image (in), - is_4D (in.ndim() == 4), - status (true) - { - if (is_4D) - image.index(3) = 0; - } - - void operator++ () - { - if (is_4D) { - image.index(3)++; - } else { - assert (status); - status = false; - } - } - operator bool() const - { - if (is_4D) - return (image.index(3) >= 0 && image.index(3) < image.size(3)); - else - return status; + void operator++() { + if (is_4D) { + image.index(3)++; + } else { + assert(status); + status = false; } + } + operator bool() const { + if (is_4D) + return (image.index(3) >= 0 && image.index(3) < image.size(3)); + else + return status; + } - private: - Image& image; - const bool is_4D; - bool status; +private: + Image ℑ + const bool is_4D; + bool status; }; - - - -void run_volume (Stats::Stats& stats, Image& data, Image& mask) -{ +void run_volume(Stats::Stats &stats, Image &data, Image &mask) { if (mask.valid()) { - for (auto l = Loop(0,3) (data, mask); l; ++l) { + for (auto l = Loop(0, 3)(data, mask); l; ++l) { if (mask.value()) - stats (data.value()); + stats(data.value()); } } else { - for (auto l = Loop(0,3) (data); l; ++l) - stats (data.value()); + for (auto l = Loop(0, 3)(data); l; ++l) + stats(data.value()); } } +void run() { - - -void run () -{ - - auto header = Header::open (argument[0]); + auto header = Header::open(argument[0]); if (header.ndim() > 4) - throw Exception ("mrstats is not designed to handle images greater than 4D"); + throw Exception("mrstats is not designed to handle images greater than 4D"); const bool is_complex = header.datatype().is_complex(); auto data = header.get_image(); const bool ignorezero = get_options("ignorezero").size(); - auto opt = get_options ("mask"); + auto opt = get_options("mask"); Image mask; if (opt.size()) { - mask = Image::open (opt[0][0]); - check_dimensions (mask, header, 0, 3); + mask = Image::open(opt[0][0]); + check_dimensions(mask, header, 0, 3); } vector fields; - opt = get_options ("output"); + opt = get_options("output"); for (size_t n = 0; n < opt.size(); ++n) - fields.push_back (opt[n][0]); + fields.push_back(opt[n][0]); if (App::log_level && fields.empty()) - Stats::print_header (is_complex); + Stats::print_header(is_complex); - if (get_options ("allvolumes").size()) { + if (get_options("allvolumes").size()) { - Stats::Stats stats (is_complex, ignorezero); - for (auto i = Volume_loop (data); i; ++i) - run_volume (stats, data, mask); - stats.print (data, fields); + Stats::Stats stats(is_complex, ignorezero); + for (auto i = Volume_loop(data); i; ++i) + run_volume(stats, data, mask); + stats.print(data, fields); } else { - for (auto i = Volume_loop (data); i; ++i) { - Stats::Stats stats (is_complex, ignorezero); - run_volume (stats, data, mask); - stats.print (data, fields); + for (auto i = Volume_loop(data); i; ++i) { + Stats::Stats stats(is_complex, ignorezero); + run_volume(stats, data, mask); + stats.print(data, fields); } - } } diff --git a/cmd/mrthreshold.cpp b/cmd/mrthreshold.cpp index 0da754d81a..fe9a63dfdf 100644 --- a/cmd/mrthreshold.cpp +++ b/cmd/mrthreshold.cpp @@ -24,224 +24,205 @@ #include "algo/loop.h" #include "filter/optimal_threshold.h" - using namespace MR; using namespace App; - enum class operator_type { LT, LE, GE, GT, UNDEFINED }; -const char* const operator_list[] = { "lt", "le", "ge", "gt", nullptr }; +const char *const operator_list[] = {"lt", "le", "ge", "gt", nullptr}; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au) and J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Create bitwise image by thresholding image intensity"; DESCRIPTION - + "The threshold value to be applied can be determined in one of a number of ways:" + +"The threshold value to be applied can be determined in one of a number of ways:" - + "- If no relevant command-line option is used, the command will automatically " - "determine an optimal threshold;" + + "- If no relevant command-line option is used, the command will automatically " + "determine an optimal threshold;" - + "- The -abs option provides the threshold value explicitly;" + + "- The -abs option provides the threshold value explicitly;" - + "- The -percentile, -top and -bottom options enable more fine-grained control " - "over how the threshold value is determined." + + "- The -percentile, -top and -bottom options enable more fine-grained control " + "over how the threshold value is determined." - + "The -mask option only influences those image values that contribute " - "toward the determination of the threshold value; once the threshold is determined, " - "it is applied to the entire image, irrespective of use of the -mask option. If you " - "wish for the voxels outside of the specified mask to additionally be excluded from " - "the output mask, this can be achieved by providing the -out_masked option." + + "The -mask option only influences those image values that contribute " + "toward the determination of the threshold value; once the threshold is determined, " + "it is applied to the entire image, irrespective of use of the -mask option. If you " + "wish for the voxels outside of the specified mask to additionally be excluded from " + "the output mask, this can be achieved by providing the -out_masked option." - + "The four operators available through the \"-comparison\" option (\"lt\", \"le\", \"ge\" and \"gt\") " - "correspond to \"less-than\" (<), \"less-than-or-equal\" (<=), \"greater-than-or-equal\" (>=) " - "and \"greater-than\" (>). This offers fine-grained control over how the thresholding " - "operation will behave in the presence of values equivalent to the threshold. " - "By default, the command will select voxels with values greater than or equal to the " - "determined threshold (\"ge\"); unless the -bottom option is used, in which case " - "after a threshold is determined from the relevant lowest-valued image voxels, those " - "voxels with values less than or equal to that threshold (\"le\") are selected. " - "This provides more fine-grained control than the -invert option; the latter " - "is provided for backwards compatibility, but is equivalent to selection of the " - "opposite comparison within this selection." + + "The four operators available through the \"-comparison\" option (\"lt\", \"le\", \"ge\" and \"gt\") " + "correspond to \"less-than\" (<), \"less-than-or-equal\" (<=), \"greater-than-or-equal\" (>=) " + "and \"greater-than\" (>). This offers fine-grained control over how the thresholding " + "operation will behave in the presence of values equivalent to the threshold. " + "By default, the command will select voxels with values greater than or equal to the " + "determined threshold (\"ge\"); unless the -bottom option is used, in which case " + "after a threshold is determined from the relevant lowest-valued image voxels, those " + "voxels with values less than or equal to that threshold (\"le\") are selected. " + "This provides more fine-grained control than the -invert option; the latter " + "is provided for backwards compatibility, but is equivalent to selection of the " + "opposite comparison within this selection." - + "If no output image path is specified, the command will instead write to " - "standard output the determined threshold value."; + + "If no output image path is specified, the command will instead write to " + "standard output the determined threshold value."; REFERENCES - + "* If not using any explicit thresholding mechanism: \n" - "Ridgway, G. R.; Omar, R.; Ourselin, S.; Hill, D. L.; Warren, J. D. & Fox, N. C. " - "Issues with threshold masking in voxel-based morphometry of atrophied brains. " - "NeuroImage, 2009, 44, 99-111"; + +"* If not using any explicit thresholding mechanism: \n" + "Ridgway, G. R.; Omar, R.; Ourselin, S.; Hill, D. L.; Warren, J. D. & Fox, N. C. " + "Issues with threshold masking in voxel-based morphometry of atrophied brains. " + "NeuroImage, 2009, 44, 99-111"; ARGUMENTS - + Argument ("input", "the input image to be thresholded").type_image_in() - + Argument ("output", "the (optional) output binary image mask").optional().type_image_out(); - + +Argument("input", "the input image to be thresholded").type_image_in() + + Argument("output", "the (optional) output binary image mask").optional().type_image_out(); OPTIONS - + OptionGroup ("Threshold determination mechanisms") - - + Option ("abs", "specify threshold value as absolute intensity") - + Argument ("value").type_float() - - + Option ("percentile", "determine threshold based on some percentile of the image intensity distribution") - + Argument ("value").type_float (0.0, 100.0) - - + Option ("top", "determine threshold that will result in selection of some number of top-valued voxels") - + Argument ("count").type_integer (1) - - + Option ("bottom", "determine & apply threshold resulting in selection of some number of bottom-valued voxels " - "(note: implies threshold application operator of \"le\" unless otherwise specified)") - + Argument ("count").type_integer (1) + +OptionGroup("Threshold determination mechanisms") + + Option("abs", "specify threshold value as absolute intensity") + Argument("value").type_float() + + Option("percentile", "determine threshold based on some percentile of the image intensity distribution") + + Argument("value").type_float(0.0, 100.0) - + OptionGroup ("Threshold determination modifiers") + + Option("top", "determine threshold that will result in selection of some number of top-valued voxels") + + Argument("count").type_integer(1) - + Option ("allvolumes", "compute a single threshold for all image volumes, rather than an individual threshold per volume") + + Option("bottom", + "determine & apply threshold resulting in selection of some number of bottom-valued voxels " + "(note: implies threshold application operator of \"le\" unless otherwise specified)") + + Argument("count").type_integer(1) - + Option ("ignorezero", "ignore zero-valued input values during threshold determination") + + OptionGroup("Threshold determination modifiers") - + Option ("mask", "compute the threshold based only on values within an input mask image") - + Argument ("image").type_image_in () + + Option("allvolumes", + "compute a single threshold for all image volumes, rather than an individual threshold per volume") + + Option("ignorezero", "ignore zero-valued input values during threshold determination") + + Option("mask", "compute the threshold based only on values within an input mask image") + + Argument("image").type_image_in() - + OptionGroup ("Threshold application modifiers") + + OptionGroup("Threshold application modifiers") - + Option ("comparison", "comparison operator to use when applying the threshold; " - "options are: " + join(operator_list, ",") + " (default = \"le\" for -bottom; \"ge\" otherwise)") - + Argument ("choice").type_choice (operator_list) + + Option("comparison", + "comparison operator to use when applying the threshold; " + "options are: " + + join(operator_list, ",") + " (default = \"le\" for -bottom; \"ge\" otherwise)") + + Argument("choice").type_choice(operator_list) - + Option ("invert", "invert the output binary mask " - "(equivalent to flipping the operator; provided for backwards compatibility)") + + Option("invert", + "invert the output binary mask " + "(equivalent to flipping the operator; provided for backwards compatibility)") - + Option ("out_masked", "mask the output image based on the provided input mask image") - - + Option ("nan", "set voxels that fail the threshold to NaN rather than zero " - "(output image will be floating-point rather than binary)"); + + Option("out_masked", "mask the output image based on the provided input mask image") + + Option("nan", + "set voxels that fail the threshold to NaN rather than zero " + "(output image will be floating-point rather than binary)"); } - using value_type = float; - - bool issue_degeneracy_warning = false; - - -Image get_mask (const Image& in) -{ +Image get_mask(const Image &in) { Image mask; - auto opt = get_options ("mask"); + auto opt = get_options("mask"); if (opt.size()) { - mask = Image::open (opt[0][0]); - check_dimensions (in, mask, 0, 3); + mask = Image::open(opt[0][0]); + check_dimensions(in, mask, 0, 3); for (size_t axis = 3; axis != mask.ndim(); ++axis) { - if (mask.size (axis) > 1 && axis < in.ndim() && mask.size (axis) != in.size (axis)) - throw Exception ("Dimensions of mask image do not match those of main image"); + if (mask.size(axis) > 1 && axis < in.ndim() && mask.size(axis) != in.size(axis)) + throw Exception("Dimensions of mask image do not match those of main image"); } } return mask; } - -vector get_data (Image& in, - Image& mask, - const size_t max_axis, - const bool ignore_zero) -{ +vector get_data(Image &in, Image &mask, const size_t max_axis, const bool ignore_zero) { vector data; - data.reserve (voxel_count (in, 0, max_axis)); + data.reserve(voxel_count(in, 0, max_axis)); if (mask.valid()) { - Adapter::Replicate> mask_replicate (mask, in); + Adapter::Replicate> mask_replicate(mask, in); if (ignore_zero) { - for (auto l = Loop(in, 0, max_axis) (in, mask_replicate); l; ++l) { - if (mask_replicate.value() && !std::isnan (static_cast(in.value())) && in.value() != 0.0f) - data.push_back (in.value()); + for (auto l = Loop(in, 0, max_axis)(in, mask_replicate); l; ++l) { + if (mask_replicate.value() && !std::isnan(static_cast(in.value())) && in.value() != 0.0f) + data.push_back(in.value()); } } else { - for (auto l = Loop(in, 0, max_axis) (in, mask_replicate); l; ++l) { - if (mask_replicate.value() && !std::isnan (static_cast(in.value()))) - data.push_back (in.value()); + for (auto l = Loop(in, 0, max_axis)(in, mask_replicate); l; ++l) { + if (mask_replicate.value() && !std::isnan(static_cast(in.value()))) + data.push_back(in.value()); } } } else { if (ignore_zero) { - for (auto l = Loop(in, 0, max_axis) (in); l; ++l) { - if (!std::isnan (static_cast(in.value())) && in.value() != 0.0f) - data.push_back (in.value()); + for (auto l = Loop(in, 0, max_axis)(in); l; ++l) { + if (!std::isnan(static_cast(in.value())) && in.value() != 0.0f) + data.push_back(in.value()); } } else { - for (auto l = Loop(in, 0, max_axis) (in); l; ++l) { - if (!std::isnan (static_cast(in.value()))) - data.push_back (in.value()); + for (auto l = Loop(in, 0, max_axis)(in); l; ++l) { + if (!std::isnan(static_cast(in.value()))) + data.push_back(in.value()); } } } if (!data.size()) - throw Exception ("No valid input data found; unable to determine threshold"); + throw Exception("No valid input data found; unable to determine threshold"); return data; } - - -default_type calculate (Image& in, - Image& mask, - const size_t max_axis, - const default_type abs, - const default_type percentile, - const ssize_t bottom, - const ssize_t top, - const bool ignore_zero) -{ - if (std::isfinite (abs)) { +default_type calculate(Image &in, + Image &mask, + const size_t max_axis, + const default_type abs, + const default_type percentile, + const ssize_t bottom, + const ssize_t top, + const bool ignore_zero) { + if (std::isfinite(abs)) { return abs; - } else if (std::isfinite (percentile)) { + } else if (std::isfinite(percentile)) { - auto data = get_data (in, mask, max_axis, ignore_zero); + auto data = get_data(in, mask, max_axis, ignore_zero); if (percentile == 100.0) { - return default_type(*std::max_element (data.begin(), data.end())); + return default_type(*std::max_element(data.begin(), data.end())); } else if (percentile == 0.0) { - return default_type(*std::min_element (data.begin(), data.end())); + return default_type(*std::min_element(data.begin(), data.end())); } else { - const default_type interp_index = 0.01 * percentile * (data.size()-1); - const size_t lower_index = size_t(std::floor (interp_index)); + const default_type interp_index = 0.01 * percentile * (data.size() - 1); + const size_t lower_index = size_t(std::floor(interp_index)); const default_type mu = interp_index - default_type(lower_index); - std::nth_element (data.begin(), data.begin() + lower_index, data.end()); + std::nth_element(data.begin(), data.begin() + lower_index, data.end()); const default_type lower_value = default_type(data[lower_index]); - std::nth_element (data.begin(), data.begin() + lower_index + 1, data.end()); + std::nth_element(data.begin(), data.begin() + lower_index + 1, data.end()); const default_type upper_value = default_type(data[lower_index + 1]); - return (1.0-mu)*lower_value + mu*upper_value; + return (1.0 - mu) * lower_value + mu * upper_value; } - } else if (std::max (bottom, top) >= 0) { + } else if (std::max(bottom, top) >= 0) { - auto data = get_data (in, mask, max_axis, ignore_zero); - const ssize_t index (bottom >= 0 ? - size_t(bottom) - 1 : - (ssize_t(data.size()) - ssize_t(top))); + auto data = get_data(in, mask, max_axis, ignore_zero); + const ssize_t index(bottom >= 0 ? size_t(bottom) - 1 : (ssize_t(data.size()) - ssize_t(top))); if (index < 0 || index >= ssize_t(data.size())) - throw Exception ("Number of valid input image values (" + str(data.size()) + ") less than number of voxels requested via -" + (bottom >= 0 ? "bottom" : "top") + " option (" + str(bottom >= 0 ? bottom : top) + ")"); - std::nth_element (data.begin(), data.begin() + index, data.end()); + throw Exception("Number of valid input image values (" + str(data.size()) + + ") less than number of voxels requested via -" + (bottom >= 0 ? "bottom" : "top") + " option (" + + str(bottom >= 0 ? bottom : top) + ")"); + std::nth_element(data.begin(), data.begin() + index, data.end()); const value_type threshold_float = data[index]; if (index) { - std::nth_element (data.begin(), data.begin() + index - 1, data.end()); - if (data[index-1] == threshold_float) + std::nth_element(data.begin(), data.begin() + index - 1, data.end()); + if (data[index - 1] == threshold_float) issue_degeneracy_warning = true; } if (index < ssize_t(data.size()) - 1) { - std::nth_element (data.begin(), data.begin() + index + 1, data.end()); - if (data[index+1] == threshold_float) + std::nth_element(data.begin(), data.begin() + index + 1, data.end()); + if (data[index + 1] == threshold_float) issue_degeneracy_warning = true; } return default_type(threshold_float); @@ -251,103 +232,107 @@ default_type calculate (Image& in, if (max_axis < in.ndim()) { // Need to extract just the current 3D volume - vector in_from (in.ndim()), in_size (in.ndim()); + vector in_from(in.ndim()), in_size(in.ndim()); size_t axis; for (axis = 0; axis != 3; ++axis) { in_from[axis] = 0; - in_size[axis] = in.size (axis); + in_size[axis] = in.size(axis); } for (; axis != in.ndim(); ++axis) { - in_from[axis] = in.index (axis); + in_from[axis] = in.index(axis); in_size[axis] = 1; } - Adapter::Subset> in_subset (in, in_from, in_size); + Adapter::Subset> in_subset(in, in_from, in_size); if (mask.valid()) { - vector mask_from (mask.ndim()), mask_size (mask.ndim()); + vector mask_from(mask.ndim()), mask_size(mask.ndim()); for (axis = 0; axis != 3; ++axis) { mask_from[axis] = 0; - mask_size[axis] = mask.size (axis); + mask_size[axis] = mask.size(axis); } for (; axis != mask.ndim(); ++axis) { - mask_from[axis] = mask.index (axis); + mask_from[axis] = mask.index(axis); mask_size[axis] = 1; } - Adapter::Subset> mask_subset (mask, mask_from, mask_size); - Adapter::Replicate mask_replicate (mask_subset, in_subset); - return Filter::estimate_optimal_threshold (in_subset, mask_replicate); + Adapter::Subset> mask_subset(mask, mask_from, mask_size); + Adapter::Replicate mask_replicate(mask_subset, in_subset); + return Filter::estimate_optimal_threshold(in_subset, mask_replicate); } else { - return Filter::estimate_optimal_threshold (in_subset); + return Filter::estimate_optimal_threshold(in_subset); } } else if (mask.valid()) { - Adapter::Replicate> mask_replicate (mask, in); - return Filter::estimate_optimal_threshold (in, mask_replicate); + Adapter::Replicate> mask_replicate(mask, in); + return Filter::estimate_optimal_threshold(in, mask_replicate); } else { - return Filter::estimate_optimal_threshold (in); + return Filter::estimate_optimal_threshold(in); } - } } - - template -void apply (Image& in, - Image& mask, - Image& out, - const size_t max_axis, - const value_type threshold, - const operator_type comp, - const bool mask_out) -{ +void apply(Image &in, + Image &mask, + Image &out, + const size_t max_axis, + const value_type threshold, + const operator_type comp, + const bool mask_out) { const T true_value = std::is_floating_point::value ? 1.0 : true; const T false_value = std::is_floating_point::value ? NaN : false; std::function func; switch (comp) { - case operator_type::LT: func = [] (const value_type in, const value_type ref) { return in < ref; }; break; - case operator_type::LE: func = [] (const value_type in, const value_type ref) { return in <= ref; }; break; - case operator_type::GE: func = [] (const value_type in, const value_type ref) { return in >= ref; }; break; - case operator_type::GT: func = [] (const value_type in, const value_type ref) { return in > ref; }; break; - case operator_type::UNDEFINED: assert (0); + case operator_type::LT: + func = [](const value_type in, const value_type ref) { return in < ref; }; + break; + case operator_type::LE: + func = [](const value_type in, const value_type ref) { return in <= ref; }; + break; + case operator_type::GE: + func = [](const value_type in, const value_type ref) { return in >= ref; }; + break; + case operator_type::GT: + func = [](const value_type in, const value_type ref) { return in > ref; }; + break; + case operator_type::UNDEFINED: + assert(0); } if (mask_out) { - assert (mask.valid()); - for (auto l = Loop(in, 0, max_axis) (in, mask, out); l; ++l) - out.value() = !std::isnan (static_cast(in.value())) && mask.value() && func (in.value(), threshold) ? true_value : false_value; + assert(mask.valid()); + for (auto l = Loop(in, 0, max_axis)(in, mask, out); l; ++l) + out.value() = !std::isnan(static_cast(in.value())) && mask.value() && func(in.value(), threshold) + ? true_value + : false_value; } else { - for (auto l = Loop(in, 0, max_axis) (in, out); l; ++l) - out.value() = !std::isnan (static_cast(in.value())) && func (in.value(), threshold) ? true_value : false_value; + for (auto l = Loop(in, 0, max_axis)(in, out); l; ++l) + out.value() = + !std::isnan(static_cast(in.value())) && func(in.value(), threshold) ? true_value : false_value; } } - - - // TODO Don't write directly to std::cout; // will get hidden by /r of progress bar // Alternatively, withhold progress bar if writing to std::cout template -void execute (Image& in, - Image& mask, - const std::string& out_path, - const default_type abs, - const default_type percentile, - const ssize_t bottom, - const ssize_t top, - const bool ignore_zero, - const bool all_volumes, - const operator_type op, - const bool mask_out) -{ +void execute(Image &in, + Image &mask, + const std::string &out_path, + const default_type abs, + const default_type percentile, + const ssize_t bottom, + const ssize_t top, + const bool ignore_zero, + const bool all_volumes, + const operator_type op, + const bool mask_out) { const bool to_cout = out_path.empty(); Image out; if (!to_cout) { - Header header_out (in); + Header header_out(in); header_out.datatype() = DataType::from(); header_out.datatype().set_byte_order_native(); - out = Image::create (out_path, header_out); + out = Image::create(out_path, header_out); } // Branch based on whether or not we need to process each image volume individually @@ -356,137 +341,136 @@ void execute (Image& in, // Do one volume at a time // If writing to cout, also add a newline between each volume if (to_cout) { - LogLevelLatch latch (App::log_level - 1); + LogLevelLatch latch(App::log_level - 1); bool is_first_loop = true; - for (auto l = Loop(3, in.ndim()) (in); l; ++l) { + for (auto l = Loop(3, in.ndim())(in); l; ++l) { if (is_first_loop) is_first_loop = false; else std::cout << "\n"; - const default_type threshold = calculate (in, mask, 3, abs, percentile, bottom, top, ignore_zero); + const default_type threshold = calculate(in, mask, 3, abs, percentile, bottom, top, ignore_zero); std::cout << threshold; } } else { - for (auto l = Loop("Determining and applying per-volume thresholds", 3, in.ndim()) (in); l; ++l) { - LogLevelLatch latch (App::log_level - 1); - const default_type threshold = calculate (in, mask, 3, abs, percentile, bottom, top, ignore_zero); - assign_pos_of (in, 3).to (out); - apply (in, mask, out, 3, value_type(threshold), op, mask_out); + for (auto l = Loop("Determining and applying per-volume thresholds", 3, in.ndim())(in); l; ++l) { + LogLevelLatch latch(App::log_level - 1); + const default_type threshold = calculate(in, mask, 3, abs, percentile, bottom, top, ignore_zero); + assign_pos_of(in, 3).to(out); + apply(in, mask, out, 3, value_type(threshold), op, mask_out); } - } return; } else if (in.ndim() <= 3 && all_volumes) { - WARN ("Option -allvolumes ignored; input image is less than 4D"); + WARN("Option -allvolumes ignored; input image is less than 4D"); } // Process whole input image as a single block - const default_type threshold = calculate (in, mask, in.ndim(), abs, percentile, bottom, top, ignore_zero); + const default_type threshold = calculate(in, mask, in.ndim(), abs, percentile, bottom, top, ignore_zero); if (to_cout) std::cout << threshold; else - apply (in, mask, out, in.ndim(), value_type(threshold), op, mask_out); - + apply(in, mask, out, in.ndim(), value_type(threshold), op, mask_out); } - - - - -void run () -{ - const default_type abs = get_option_value ("abs", NaN); - const default_type percentile = get_option_value ("percentile", NaN); - const ssize_t bottom = get_option_value ("bottom", -1); - const ssize_t top = get_option_value ("top", -1); - const size_t num_explicit_mechanisms = (std::isfinite (abs) ? 1 : 0) + - (std::isfinite (percentile) ? 1 : 0) + - (bottom >= 0 ? 1 : 0) + - (top >= 0 ? 1 : 0); +void run() { + const default_type abs = get_option_value("abs", NaN); + const default_type percentile = get_option_value("percentile", NaN); + const ssize_t bottom = get_option_value("bottom", -1); + const ssize_t top = get_option_value("top", -1); + const size_t num_explicit_mechanisms = + (std::isfinite(abs) ? 1 : 0) + (std::isfinite(percentile) ? 1 : 0) + (bottom >= 0 ? 1 : 0) + (top >= 0 ? 1 : 0); if (num_explicit_mechanisms > 1) - throw Exception ("Cannot specify more than one mechanism for threshold selection"); + throw Exception("Cannot specify more than one mechanism for threshold selection"); - auto header_in = Header::open (argument[0]); + auto header_in = Header::open(argument[0]); if (header_in.datatype().is_complex()) - throw Exception ("Cannot perform thresholding directly on complex image data"); + throw Exception("Cannot perform thresholding directly on complex image data"); auto in = header_in.get_image(); const bool to_cout = argument.size() == 1; const std::string output_path = to_cout ? std::string("") : argument[1]; const bool all_volumes = get_options("allvolumes").size(); const bool ignore_zero = get_options("ignorezero").size(); - const bool use_nan = get_options ("nan").size(); - const bool invert = get_options ("invert").size(); + const bool use_nan = get_options("nan").size(); + const bool invert = get_options("invert").size(); - bool mask_out = get_options ("out_masked").size(); + bool mask_out = get_options("out_masked").size(); - auto opt = get_options ("comparison"); - operator_type comp = opt.size() ? - operator_type(int(opt[0][0])) : - (bottom >= 0 ? operator_type::LE : operator_type::GE); + auto opt = get_options("comparison"); + operator_type comp = + opt.size() ? operator_type(int(opt[0][0])) : (bottom >= 0 ? operator_type::LE : operator_type::GE); if (invert) { switch (comp) { - case operator_type::LT: comp = operator_type::GE; break; - case operator_type::LE: comp = operator_type::GT; break; - case operator_type::GE: comp = operator_type::LT; break; - case operator_type::GT: comp = operator_type::LE; break; - case operator_type::UNDEFINED: assert (0); + case operator_type::LT: + comp = operator_type::GE; + break; + case operator_type::LE: + comp = operator_type::GT; + break; + case operator_type::GE: + comp = operator_type::LT; + break; + case operator_type::GT: + comp = operator_type::LE; + break; + case operator_type::UNDEFINED: + assert(0); } } if (to_cout) { if (use_nan) { - WARN ("Option -nan ignored: has no influence when no output image is specified"); + WARN("Option -nan ignored: has no influence when no output image is specified"); } if (opt.size()) { - WARN ("Option -comparison ignored: has no influence when no output image is specified"); + WARN("Option -comparison ignored: has no influence when no output image is specified"); comp = operator_type::UNDEFINED; } if (invert) { - WARN ("Option -invert ignored: has no influence when no output image is specified"); + WARN("Option -invert ignored: has no influence when no output image is specified"); } if (mask_out) { - WARN ("Option -out_masked ignored: has no influence when no output image is specified"); + WARN("Option -out_masked ignored: has no influence when no output image is specified"); } } Image mask; - if (std::isfinite (abs)) { + if (std::isfinite(abs)) { if (ignore_zero) { - WARN ("-ignorezero option has no effect if combined with -abs option"); + WARN("-ignorezero option has no effect if combined with -abs option"); } - if (get_options ("mask").size() && !mask_out) { - WARN ("-mask option has no effect if combined with -abs option and -out_masked is not used"); + if (get_options("mask").size() && !mask_out) { + WARN("-mask option has no effect if combined with -abs option and -out_masked is not used"); } } else { - mask = get_mask (in); + mask = get_mask(in); if (!mask.valid() && mask_out) { - WARN ("-out_masked option ignored; no mask image provided via -mask"); + WARN("-out_masked option ignored; no mask image provided via -mask"); mask_out = false; } if (!num_explicit_mechanisms) { if (ignore_zero) { - WARN ("Option -ignorezero ignored by automatic threshold calculation"); + WARN("Option -ignorezero ignored by automatic threshold calculation"); } try { - check_3D_nonunity (in); - } catch (Exception& e) { - throw Exception (e, "Automatic thresholding can only be performed for voxel data"); + check_3D_nonunity(in); + } catch (Exception &e) { + throw Exception(e, "Automatic thresholding can only be performed for voxel data"); } } } if (use_nan) - execute (in, mask, output_path, abs, percentile, bottom, top, ignore_zero, all_volumes, comp, mask_out); + execute(in, mask, output_path, abs, percentile, bottom, top, ignore_zero, all_volumes, comp, mask_out); else - execute (in, mask, output_path, abs, percentile, bottom, top, ignore_zero, all_volumes, comp, mask_out); + execute(in, mask, output_path, abs, percentile, bottom, top, ignore_zero, all_volumes, comp, mask_out); if (issue_degeneracy_warning) { - WARN ("Duplicate image values surrounding threshold; " - "exact number of voxels influenced by numerical threshold may not match requested number"); + WARN("Duplicate image values surrounding threshold; " + "exact number of voxels influenced by numerical threshold may not match requested number"); } } diff --git a/cmd/mrtransform.cpp b/cmd/mrtransform.cpp index b5bbd84865..073d03e5fd 100644 --- a/cmd/mrtransform.cpp +++ b/cmd/mrtransform.cpp @@ -14,278 +14,292 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "progressbar.h" -#include "image.h" -#include "file/matrix.h" -#include "math/math.h" -#include "math/sphere.h" -#include "interp/nearest.h" -#include "interp/linear.h" -#include "interp/cubic.h" -#include "interp/sinc.h" -#include "filter/reslice.h" -#include "filter/warp.h" -#include "algo/loop.h" +#include "adapter/jacobian.h" #include "algo/copy.h" +#include "algo/loop.h" #include "algo/threaded_copy.h" +#include "command.h" #include "dwi/directions/predefined.h" #include "dwi/gradient.h" +#include "file/matrix.h" +#include "file/nifti_utils.h" +#include "filter/reslice.h" +#include "filter/warp.h" +#include "image.h" +#include "interp/cubic.h" +#include "interp/linear.h" +#include "interp/nearest.h" +#include "interp/sinc.h" +#include "math/SH.h" +#include "math/average_space.h" +#include "math/math.h" +#include "math/sphere.h" +#include "progressbar.h" #include "registration/transform/reorient.h" -#include "registration/warp/helpers.h" #include "registration/warp/compose.h" -#include "math/average_space.h" -#include "math/SH.h" -#include "adapter/jacobian.h" -#include "file/nifti_utils.h" - - +#include "registration/warp/helpers.h" using namespace MR; using namespace App; -const char* interp_choices[] = { "nearest", "linear", "cubic", "sinc", nullptr }; -const char* modulation_choices[] = { "fod", "jac", nullptr }; +const char *interp_choices[] = {"nearest", "linear", "cubic", "sinc", nullptr}; +const char *modulation_choices[] = {"fod", "jac", nullptr}; -void usage () -{ +void usage() { - AUTHOR = "J-Donald Tournier (jdtournier@gmail.com) and David Raffelt (david.raffelt@florey.edu.au) and Max Pietsch (maximilian.pietsch@kcl.ac.uk)"; + AUTHOR = "J-Donald Tournier (jdtournier@gmail.com) and David Raffelt (david.raffelt@florey.edu.au) and Max Pietsch " + "(maximilian.pietsch@kcl.ac.uk)"; SYNOPSIS = "Apply spatial transformations to an image"; DESCRIPTION - + "If a linear transform is applied without a template image the command " - "will modify the image header transform matrix" - - + "FOD reorientation (with apodised point spread functions) can be performed " - "if the number of volumes in the 4th dimension equals the number " - "of coefficients in an antipodally symmetric spherical harmonic series (e.g. " - "6, 15, 28 etc). For such data, the -reorient_fod yes/no option must be used to specify " - "if reorientation is required." - - + "The output image intensity can be modulated using the (local or global) volume change " - "if a linear or nonlinear transformation is applied. 'FOD' modulation preserves the " - "apparent fibre density across the fibre bundle width and can only be applied if FOD reorientation " - "is used. Alternatively, non-directional scaling by the Jacobian determinant can be applied to " - "any image type. " - - + "If a DW scheme is contained in the header (or specified separately), and " - "the number of directions matches the number of volumes in the images, any " - "transformation applied using the -linear option will also be applied to the directions." - - + "When the -template option is used to specify the target image grid, the " - "image provided via this option will not influence the axis data strides " - "of the output image; these are determined based on the input image, or the " - "input to the -strides option."; + +"If a linear transform is applied without a template image the command " + "will modify the image header transform matrix" + + + "FOD reorientation (with apodised point spread functions) can be performed " + "if the number of volumes in the 4th dimension equals the number " + "of coefficients in an antipodally symmetric spherical harmonic series (e.g. " + "6, 15, 28 etc). For such data, the -reorient_fod yes/no option must be used to specify " + "if reorientation is required." + + + "The output image intensity can be modulated using the (local or global) volume change " + "if a linear or nonlinear transformation is applied. 'FOD' modulation preserves the " + "apparent fibre density across the fibre bundle width and can only be applied if FOD reorientation " + "is used. Alternatively, non-directional scaling by the Jacobian determinant can be applied to " + "any image type. " + + + "If a DW scheme is contained in the header (or specified separately), and " + "the number of directions matches the number of volumes in the images, any " + "transformation applied using the -linear option will also be applied to the directions." + + + "When the -template option is used to specify the target image grid, the " + "image provided via this option will not influence the axis data strides " + "of the output image; these are determined based on the input image, or the " + "input to the -strides option."; REFERENCES - + "* If FOD reorientation is being performed:\n" - "Raffelt, D.; Tournier, J.-D.; Crozier, S.; Connelly, A. & Salvado, O. " // Internal - "Reorientation of fiber orientation distributions using apodized point spread functions. " - "Magnetic Resonance in Medicine, 2012, 67, 844-855" + +"* If FOD reorientation is being performed:\n" + "Raffelt, D.; Tournier, J.-D.; Crozier, S.; Connelly, A. & Salvado, O. " // Internal + "Reorientation of fiber orientation distributions using apodized point spread functions. " + "Magnetic Resonance in Medicine, 2012, 67, 844-855" - + "* If FOD modulation is being performed:\n" - "Raffelt, D.; Tournier, J.-D.; Rose, S.; Ridgway, G.R.; Henderson, R.; Crozier, S.; Salvado, O.; Connelly, A.; " // Internal - "Apparent Fibre Density: a novel measure for the analysis of diffusion-weighted magnetic resonance images. " - "NeuroImage, 2012, 15;59(4), 3976-94"; + + + "* If FOD modulation is being performed:\n" + "Raffelt, D.; Tournier, J.-D.; Rose, S.; Ridgway, G.R.; Henderson, R.; Crozier, S.; Salvado, O.; Connelly, A.; " // Internal + "Apparent Fibre Density: a novel measure for the analysis of diffusion-weighted magnetic resonance images. " + "NeuroImage, 2012, 15;59(4), 3976-94"; ARGUMENTS - + Argument ("input", "input image to be transformed.").type_image_in () - + Argument ("output", "the output image.").type_image_out (); + +Argument("input", "input image to be transformed.").type_image_in() + + Argument("output", "the output image.").type_image_out(); OPTIONS - + OptionGroup ("Affine transformation options") - - + Option ("linear", - "specify a linear transform to apply, in the form of a 3x4 " - "or 4x4 ascii file. Note the standard 'reverse' convention " - "is used, where the transform maps points in the template image " - "to the moving image. Note that the reverse convention is still " - "assumed even if no -template image is supplied") - + Argument ("transform").type_file_in () - - + Option ("flip", - "flip the specified axes, provided as a comma-separated list of indices (0:x, 1:y, 2:z).") - + Argument ("axes").type_sequence_int() - - + Option ("inverse", - "apply the inverse transformation") - - + Option ("half", - "apply the matrix square root of the transformation. This can be combined with the inverse option.") - - + Option ("replace", - "replace the linear transform of the original image by that specified, " - "rather than applying it to the original image. The specified transform " - "can be either a template image, or a 3x4 or 4x4 ascii file.") - + Argument ("file").type_file_in() - - + Option ("identity", - "set the header transform of the image to the identity matrix") - - + OptionGroup ("Regridding options") - - + Option ("template", - "reslice the input image to match the specified template image grid.") - + Argument ("image").type_image_in () - - + Option ("midway_space", - "reslice the input image to the midway space. Requires either the -template or -warp option. If " - "used with -template and -linear option the input image will be resliced onto the grid halfway between the input and template. " - "If used with the -warp option the input will be warped to the midway space defined by the grid of the input warp " - "(i.e. half way between image1 and image2)") - - + Option ("interp", - "set the interpolation method to use when reslicing (choices: nearest, linear, cubic, sinc. Default: cubic).") - + Argument ("method").type_choice (interp_choices) - - + Option ("oversample", - "set the amount of over-sampling (in the target space) to perform when regridding. This is particularly " - "relevant when downsamping a high-resolution image to a low-resolution image, to avoid aliasing artefacts. " - "This can consist of a single integer, or a comma-separated list of 3 integers if different oversampling " - "factors are desired along the different axes. Default is determined from ratio of voxel dimensions (disabled " - "for nearest-neighbour interpolation).") - + Argument ("factor").type_sequence_int() - - + OptionGroup ("Non-linear transformation options") - - // TODO point users to a documentation page describing the warp field format - + Option ("warp", - "apply a non-linear 4D deformation field to warp the input image. Each voxel in the deformation field must define " - "the scanner space position that will be used to interpolate the input image during warping (i.e. pull-back/reverse warp convention). " - "If the -template image is also supplied the deformation field will be resliced first to the template image grid. If no -template " - "option is supplied then the output image will have the same image grid as the deformation field. This option can be used in " - "combination with the -affine option, in which case the affine will be applied first)") - + Argument ("image").type_image_in () - - + Option ("warp_full", - "warp the input image using a 5D warp file output from mrregister. Any linear transforms in the warp image header " - "will also be applied. The -warp_full option must be used in combination with either the -template option or the -midway_space option. " - "If a -template image is supplied then the full warp will be used. By default the image1->image2 transform will be applied, " - "however the -from 2 option can be used to apply the image2->image1 transform. Use the -midway_space option to warp the input " - "image to the midway space. The -from option can also be used to define which warp to use when transforming to midway space") - + Argument ("image").type_image_in () - - + Option ("from", - "used to define which space the input image is when using the -warp_mid option. " - "Use -from 1 to warp from image1 or -from 2 to warp from image2") - + Argument ("image").type_integer (1,2) - - + OptionGroup ("Fibre orientation distribution handling options") - - + Option ("modulate", - "Valid choices are: fod and jac. \n" - "fod: modulate FODs during reorientation to preserve the apparent fibre density across fibre bundle widths before and after the transformation. \n" - "jac: modulate the image intensity with the determinant of the Jacobian of the warp of linear transformation " - "to preserve the total intensity before and after the transformation.") - + Argument ("method").type_choice (modulation_choices) - - + Option ("directions", - "directions defining the number and orientation of the apodised point spread functions used in FOD reorientation " - "(Default: 300 directions)") - + Argument ("file", "a list of directions [az el] generated using the dirgen command.").type_file_in() - - + Option ("reorient_fod", - "specify whether to perform FOD reorientation. This is required if the number " - "of volumes in the 4th dimension corresponds to the number of coefficients in an " - "antipodally symmetric spherical harmonic series with lmax >= 2 (i.e. 6, 15, 28, 45, 66 volumes).") - + Argument ("boolean").type_bool() - - + DWI::GradImportOptions() - - + DWI::GradExportOptions() - - + DataType::options () - - + Stride::Options - - + OptionGroup ("Additional generic options for mrtransform") - - + Option ("nan", - "Use NaN as the out of bounds value (Default: 0.0)") - - + Option ("no_reorientation", "deprecated, use -reorient_fod instead"); + +OptionGroup("Affine transformation options") + + + Option("linear", + "specify a linear transform to apply, in the form of a 3x4 " + "or 4x4 ascii file. Note the standard 'reverse' convention " + "is used, where the transform maps points in the template image " + "to the moving image. Note that the reverse convention is still " + "assumed even if no -template image is supplied") + + Argument("transform").type_file_in() + + + Option("flip", "flip the specified axes, provided as a comma-separated list of indices (0:x, 1:y, 2:z).") + + Argument("axes").type_sequence_int() + + + Option("inverse", "apply the inverse transformation") + + + Option("half", + "apply the matrix square root of the transformation. This can be combined with the inverse option.") + + + Option("replace", + "replace the linear transform of the original image by that specified, " + "rather than applying it to the original image. The specified transform " + "can be either a template image, or a 3x4 or 4x4 ascii file.") + + Argument("file").type_file_in() + + + Option("identity", "set the header transform of the image to the identity matrix") + + + OptionGroup("Regridding options") + + + Option("template", "reslice the input image to match the specified template image grid.") + + Argument("image").type_image_in() + + + Option("midway_space", + "reslice the input image to the midway space. Requires either the -template or -warp option. If " + "used with -template and -linear option the input image will be resliced onto the grid halfway between " + "the input and template. " + "If used with the -warp option the input will be warped to the midway space defined by the grid of the " + "input warp " + "(i.e. half way between image1 and image2)") + + + Option("interp", + "set the interpolation method to use when reslicing (choices: nearest, linear, cubic, sinc. Default: " + "cubic).") + + Argument("method").type_choice(interp_choices) + + + Option( + "oversample", + "set the amount of over-sampling (in the target space) to perform when regridding. This is particularly " + "relevant when downsamping a high-resolution image to a low-resolution image, to avoid aliasing artefacts. " + "This can consist of a single integer, or a comma-separated list of 3 integers if different oversampling " + "factors are desired along the different axes. Default is determined from ratio of voxel dimensions " + "(disabled " + "for nearest-neighbour interpolation).") + + Argument("factor").type_sequence_int() + + + OptionGroup("Non-linear transformation options") + + // TODO point users to a documentation page describing the warp field format + + Option("warp", + "apply a non-linear 4D deformation field to warp the input image. Each voxel in the deformation field " + "must define " + "the scanner space position that will be used to interpolate the input image during warping (i.e. " + "pull-back/reverse warp convention). " + "If the -template image is also supplied the deformation field will be resliced first to the template " + "image grid. If no -template " + "option is supplied then the output image will have the same image grid as the deformation field. This " + "option can be used in " + "combination with the -affine option, in which case the affine will be applied first)") + + Argument("image").type_image_in() + + + Option("warp_full", + "warp the input image using a 5D warp file output from mrregister. Any linear transforms in the warp " + "image header " + "will also be applied. The -warp_full option must be used in combination with either the -template " + "option or the -midway_space option. " + "If a -template image is supplied then the full warp will be used. By default the image1->image2 " + "transform will be applied, " + "however the -from 2 option can be used to apply the image2->image1 transform. Use the -midway_space " + "option to warp the input " + "image to the midway space. The -from option can also be used to define which warp to use when " + "transforming to midway space") + + Argument("image").type_image_in() + + + Option("from", + "used to define which space the input image is when using the -warp_mid option. " + "Use -from 1 to warp from image1 or -from 2 to warp from image2") + + Argument("image").type_integer(1, 2) + + + OptionGroup("Fibre orientation distribution handling options") + + + + Option( + "modulate", + "Valid choices are: fod and jac. \n" + "fod: modulate FODs during reorientation to preserve the apparent fibre density across fibre bundle widths " + "before and after the transformation. \n" + "jac: modulate the image intensity with the determinant of the Jacobian of the warp of linear transformation " + "to preserve the total intensity before and after the transformation.") + + Argument("method").type_choice(modulation_choices) + + + Option("directions", + "directions defining the number and orientation of the apodised point spread functions used in FOD " + "reorientation " + "(Default: 300 directions)") + + Argument("file", "a list of directions [az el] generated using the dirgen command.").type_file_in() + + + Option("reorient_fod", + "specify whether to perform FOD reorientation. This is required if the number " + "of volumes in the 4th dimension corresponds to the number of coefficients in an " + "antipodally symmetric spherical harmonic series with lmax >= 2 (i.e. 6, 15, 28, 45, 66 volumes).") + + Argument("boolean").type_bool() + + + DWI::GradImportOptions() + + + DWI::GradExportOptions() + + + DataType::options() + + + Stride::Options + + + OptionGroup("Additional generic options for mrtransform") + + + Option("nan", "Use NaN as the out of bounds value (Default: 0.0)") + + + Option("no_reorientation", "deprecated, use -reorient_fod instead"); } -void apply_warp (Image& input, Image& output, Image& warp, - const int interp, const float out_of_bounds_value, const vector& oversample, const bool jacobian_modulate = false) { +void apply_warp(Image &input, + Image &output, + Image &warp, + const int interp, + const float out_of_bounds_value, + const vector &oversample, + const bool jacobian_modulate = false) { switch (interp) { case 0: - Filter::warp (input, output, warp, out_of_bounds_value, oversample, jacobian_modulate); + Filter::warp(input, output, warp, out_of_bounds_value, oversample, jacobian_modulate); break; case 1: - Filter::warp (input, output, warp, out_of_bounds_value, oversample, jacobian_modulate); + Filter::warp(input, output, warp, out_of_bounds_value, oversample, jacobian_modulate); break; case 2: - Filter::warp (input, output, warp, out_of_bounds_value, oversample, jacobian_modulate); + Filter::warp(input, output, warp, out_of_bounds_value, oversample, jacobian_modulate); break; case 3: - Filter::warp (input, output, warp, out_of_bounds_value, oversample, jacobian_modulate); + Filter::warp(input, output, warp, out_of_bounds_value, oversample, jacobian_modulate); break; default: - assert (0); + assert(0); break; } } -void apply_linear_jacobian (Image & image, transform_type trafo) { - const float det = trafo.linear().topLeftCorner<3,3>().determinant(); +void apply_linear_jacobian(Image &image, transform_type trafo) { + const float det = trafo.linear().topLeftCorner<3, 3>().determinant(); INFO("global intensity modulation with scale factor " + str(det)); - for (auto i = Loop ("applying global intensity modulation", image, 0, image.ndim()) (image); i; ++i) { + for (auto i = Loop("applying global intensity modulation", image, 0, image.ndim())(image); i; ++i) { image.value() *= det; } } - - -void run () -{ - auto input_header = Header::open (argument[0]); - Header output_header (input_header); - output_header.datatype() = DataType::from_command_line (DataType::from ()); - Stride::set_from_command_line (output_header); +void run() { + auto input_header = Header::open(argument[0]); + Header output_header(input_header); + output_header.datatype() = DataType::from_command_line(DataType::from()); + Stride::set_from_command_line(output_header); // Linear transform_type linear_transform = transform_type::Identity(); bool linear = false; - auto opt = get_options ("linear"); + auto opt = get_options("linear"); if (opt.size()) { linear = true; - linear_transform = File::Matrix::load_transform (opt[0][0]); + linear_transform = File::Matrix::load_transform(opt[0][0]); } // Replace bool replace = false; - opt = get_options ("replace"); + opt = get_options("replace"); if (opt.size()) { linear = replace = true; try { - auto template_header = Header::open (opt[0][0]); + auto template_header = Header::open(opt[0][0]); linear_transform = template_header.transform(); } catch (...) { try { - linear_transform = File::Matrix::load_transform (opt[0][0]); + linear_transform = File::Matrix::load_transform(opt[0][0]); } catch (...) { - throw Exception ("Unable to extract transform matrix from -replace file \"" + str(opt[0][0]) + "\""); + throw Exception("Unable to extract transform matrix from -replace file \"" + str(opt[0][0]) + "\""); } } } - if (get_options ("identity").size()) { + if (get_options("identity").size()) { linear = replace = true; linear_transform.setIdentity(); } // Template - opt = get_options ("template"); + opt = get_options("template"); Header template_header; if (opt.size()) { if (replace) - throw Exception ("you cannot use the -replace option with the -template option"); + throw Exception("you cannot use the -replace option with the -template option"); if (!linear) linear_transform.setIdentity(); - template_header = Header::open (opt[0][0]); + template_header = Header::open(opt[0][0]); for (size_t i = 0; i < 3; ++i) { output_header.size(i) = template_header.size(i); output_header.spacing(i) = template_header.spacing(i); @@ -295,79 +309,79 @@ void run () // Warp 5D warp // TODO add reference to warp format documentation - opt = get_options ("warp_full"); + opt = get_options("warp_full"); Image warp; if (opt.size()) { - if (!Path::is_mrtrix_image (opt[0][0]) && !(Path::has_suffix (opt[0][0], {".nii", ".nii.gz"}) && - File::Config::get_bool ("NIfTIAutoLoadJSON", false) && - Path::exists(File::NIfTI::get_json_path(opt[0][0])))) - WARN ("warp_full image is not in original .mif/.mih file format or in NIfTI file format with associated JSON. " - "Converting to other file formats may remove linear transformations stored in the image header."); - warp = Image::open (opt[0][0]).with_direct_io(); - Registration::Warp::check_warp_full (warp); + if (!Path::is_mrtrix_image(opt[0][0]) && + !(Path::has_suffix(opt[0][0], {".nii", ".nii.gz"}) && File::Config::get_bool("NIfTIAutoLoadJSON", false) && + Path::exists(File::NIfTI::get_json_path(opt[0][0])))) + WARN("warp_full image is not in original .mif/.mih file format or in NIfTI file format with associated JSON. " + "Converting to other file formats may remove linear transformations stored in the image header."); + warp = Image::open(opt[0][0]).with_direct_io(); + Registration::Warp::check_warp_full(warp); if (linear) - throw Exception ("the -warp_full option cannot be applied in combination with -linear since the " - "linear transform is already included in the warp header"); + throw Exception("the -warp_full option cannot be applied in combination with -linear since the " + "linear transform is already included in the warp header"); } // Warp from image1 or image2 int from = 1; - opt = get_options ("from"); + opt = get_options("from"); if (opt.size()) { from = opt[0][0]; if (!warp.valid()) - WARN ("-from option ignored since no 5D warp was input"); + WARN("-from option ignored since no 5D warp was input"); } // Warp deformation field - opt = get_options ("warp"); + opt = get_options("warp"); if (opt.size()) { if (warp.valid()) - throw Exception ("only one warp field can be input with either -warp or -warp_mid"); - warp = Image::open (opt[0][0]).with_direct_io (Stride::contiguous_along_axis(3)); + throw Exception("only one warp field can be input with either -warp or -warp_mid"); + warp = Image::open(opt[0][0]).with_direct_io(Stride::contiguous_along_axis(3)); if (warp.ndim() != 4) - throw Exception ("the input -warp file must be a 4D deformation field"); + throw Exception("the input -warp file must be a 4D deformation field"); if (warp.size(3) != 3) - throw Exception ("the input -warp file must have 3 volumes in the 4th dimension (x,y,z positions)"); + throw Exception("the input -warp file must have 3 volumes in the 4th dimension (x,y,z positions)"); } // Inverse - const bool inverse = get_options ("inverse").size(); + const bool inverse = get_options("inverse").size(); if (inverse) { if (!(linear || warp.valid())) - throw Exception ("no linear or warp transformation provided for option '-inverse'"); + throw Exception("no linear or warp transformation provided for option '-inverse'"); if (replace) - throw Exception ("cannot use -inverse option in conjunction with -replace or -identity options"); + throw Exception("cannot use -inverse option in conjunction with -replace or -identity options"); if (warp.valid()) if (warp.ndim() == 4) - throw Exception ("cannot apply -inverse with the input -warp_df deformation field."); + throw Exception("cannot apply -inverse with the input -warp_df deformation field."); linear_transform = linear_transform.inverse(); } // Half - const bool half = get_options ("half").size(); + const bool half = get_options("half").size(); if (half) { if (!(linear)) - throw Exception ("no linear transformation provided for option '-half'"); + throw Exception("no linear transformation provided for option '-half'"); if (replace) - throw Exception ("cannot use -half option in conjunction with -replace or -identity options"); + throw Exception("cannot use -half option in conjunction with -replace or -identity options"); Eigen::Matrix temp; temp.row(3) << 0, 0, 0, 1.0; - temp.topLeftCorner(3,4) = linear_transform.matrix().topLeftCorner(3,4); - linear_transform.matrix() = temp.sqrt().topLeftCorner(3,4); + temp.topLeftCorner(3, 4) = linear_transform.matrix().topLeftCorner(3, 4); + linear_transform.matrix() = temp.sqrt().topLeftCorner(3, 4); } // Flip - opt = get_options ("flip"); + opt = get_options("flip"); vector axes; if (opt.size()) { - axes = parse_ints (opt[0][0]); + axes = parse_ints(opt[0][0]); transform_type flip; flip.setIdentity(); for (size_t i = 0; i < axes.size(); ++i) { if (axes[i] < 0 || axes[i] > 2) - throw Exception ("axes supplied to -flip are out of bounds (" + std::string (opt[0][0]) + ")"); - flip(axes[i],3) += flip(axes[i],axes[i]) * input_header.spacing(axes[i]) * (input_header.size(axes[i])-1); + throw Exception("axes supplied to -flip are out of bounds (" + std::string(opt[0][0]) + ")"); + flip(axes[i], 3) += flip(axes[i], axes[i]) * input_header.spacing(axes[i]) * (input_header.size(axes[i]) - 1); flip(axes[i], axes[i]) *= -1.0; } if (!replace) @@ -380,70 +394,68 @@ void run () linear_transform = linear_transform * flip; } - Stride::List stride = Stride::get (input_header); + Stride::List stride = Stride::get(input_header); // Detect FOD image bool is_possible_fod_image = input_header.ndim() == 4 && input_header.size(3) >= 6 && - input_header.size(3) == (int) Math::SH::NforL (Math::SH::LforN (input_header.size(3))); - + input_header.size(3) == (int)Math::SH::NforL(Math::SH::LforN(input_header.size(3))); // reorientation - if (get_options ("no_reorientation").size()) - throw Exception ("The -no_reorientation option is deprecated. Use -reorient_fod no instead."); - opt = get_options ("reorient_fod"); + if (get_options("no_reorientation").size()) + throw Exception("The -no_reorientation option is deprecated. Use -reorient_fod no instead."); + opt = get_options("reorient_fod"); bool fod_reorientation = opt.size() && bool(opt[0][0]); if (is_possible_fod_image && !opt.size()) throw Exception("-reorient_fod yes/no needs to be explicitly specified for images with " + - str(input_header.size(3)) + " volumes"); + str(input_header.size(3)) + " volumes"); else if (!is_possible_fod_image && fod_reorientation) throw Exception("Apodised PSF reorientation requires SH series images"); Eigen::MatrixXd directions_cartesian; if (fod_reorientation && (linear || warp.valid() || template_header.valid()) && is_possible_fod_image) { - CONSOLE ("performing apodised PSF reorientation"); + CONSOLE("performing apodised PSF reorientation"); Eigen::MatrixXd directions_az_el; - opt = get_options ("directions"); + opt = get_options("directions"); if (opt.size()) - directions_az_el = File::Matrix::load_matrix (opt[0][0]); + directions_az_el = File::Matrix::load_matrix(opt[0][0]); else directions_az_el = DWI::Directions::electrostatic_repulsion_300(); - Math::Sphere::spherical2cartesian (directions_az_el, directions_cartesian); + Math::Sphere::spherical2cartesian(directions_az_el, directions_cartesian); // load with SH coeffients contiguous in RAM - stride = Stride::contiguous_along_axis (3, input_header); + stride = Stride::contiguous_along_axis(3, input_header); } // Intensity / FOD modulation - opt = get_options ("modulate"); - bool modulate_fod = opt.size() && (int) opt[0][0] == 0; - bool modulate_jac = opt.size() && (int) opt[0][0] == 1; + opt = get_options("modulate"); + bool modulate_fod = opt.size() && (int)opt[0][0] == 0; + bool modulate_jac = opt.size() && (int)opt[0][0] == 1; const std::string reorient_msg = str("reorienting") + str((modulate_fod ? " with FOD modulation" : "")); if (modulate_fod) - add_line (output_header.keyval()["comments"], std::string ("FOD modulation applied")); + add_line(output_header.keyval()["comments"], std::string("FOD modulation applied")); if (modulate_jac) - add_line (output_header.keyval()["comments"], std::string ("Jacobian determinant modulation applied")); + add_line(output_header.keyval()["comments"], std::string("Jacobian determinant modulation applied")); if (modulate_fod) { if (!is_possible_fod_image) - throw Exception ("FOD modulation can only be performed with SH series image"); + throw Exception("FOD modulation can only be performed with SH series image"); if (!fod_reorientation) - throw Exception ("FOD modulation can only be performed with FOD reorientation"); + throw Exception("FOD modulation can only be performed with FOD reorientation"); } if (modulate_jac) { if (fod_reorientation) { - WARN ("Input image being interpreted as FOD data (user requested FOD reorientation); " - "FOD-based modulation would be more appropriate for such data than the requested Jacobian modulation."); + WARN("Input image being interpreted as FOD data (user requested FOD reorientation); " + "FOD-based modulation would be more appropriate for such data than the requested Jacobian modulation."); } else if (is_possible_fod_image) { - WARN ("Jacobian modulation performed on possible SH series image. Did you mean FOD modulation?"); + WARN("Jacobian modulation performed on possible SH series image. Did you mean FOD modulation?"); } if (!linear && !warp.valid()) - throw Exception ("Jacobian modulation requires linear or nonlinear transformation"); + throw Exception("Jacobian modulation requires linear or nonlinear transformation"); } - // Rotate/Flip direction information if present if (linear && input_header.ndim() == 4 && !warp && !fod_reorientation) { Eigen::MatrixXd rotation = linear_transform.linear().inverse(); @@ -455,131 +467,131 @@ void run () // Diffusion gradient table Eigen::MatrixXd grad; try { - grad = DWI::get_DW_scheme (input_header); - } catch (Exception&) {} + grad = DWI::get_DW_scheme(input_header); + } catch (Exception &) { + } if (grad.rows()) { try { - if (input_header.size(3) != (ssize_t) grad.rows()) { - throw Exception ("DW gradient table of different length (" - + str(grad.rows()) - + ") to number of image volumes (" - + str(input_header.size(3)) - + ")"); + if (input_header.size(3) != (ssize_t)grad.rows()) { + throw Exception("DW gradient table of different length (" + str(grad.rows()) + + ") to number of image volumes (" + str(input_header.size(3)) + ")"); } - INFO ("DW gradients detected and will be reoriented"); - if (!test.isIdentity (0.001)) { - WARN ("the input linear transform contains shear or anisotropic scaling and " - "therefore should not be used to reorient directions / diffusion gradients"); + INFO("DW gradients detected and will be reoriented"); + if (!test.isIdentity(0.001)) { + WARN("the input linear transform contains shear or anisotropic scaling and " + "therefore should not be used to reorient directions / diffusion gradients"); } for (ssize_t n = 0; n < grad.rows(); ++n) { - Eigen::Vector3d grad_vector = grad.block<1,3>(n,0); - grad.block<1,3>(n,0) = rotation * grad_vector; + Eigen::Vector3d grad_vector = grad.block<1, 3>(n, 0); + grad.block<1, 3>(n, 0) = rotation * grad_vector; } - DWI::set_DW_scheme (output_header, grad); - } - catch (Exception& e) { - e.display (2); - WARN ("DW gradients not correctly reoriented"); + DWI::set_DW_scheme(output_header, grad); + } catch (Exception &e) { + e.display(2); + WARN("DW gradients not correctly reoriented"); } } // Also look for key 'directions', and rotate those if present - auto hit = input_header.keyval().find ("directions"); + auto hit = input_header.keyval().find("directions"); if (hit != input_header.keyval().end()) { - INFO ("Header entry \"directions\" detected and will be reoriented"); - if (!test.isIdentity (0.001)) { - WARN ("the input linear transform contains shear or anisotropic scaling and " - "therefore should not be used to reorient directions / diffusion gradients"); + INFO("Header entry \"directions\" detected and will be reoriented"); + if (!test.isIdentity(0.001)) { + WARN("the input linear transform contains shear or anisotropic scaling and " + "therefore should not be used to reorient directions / diffusion gradients"); } try { - const auto lines = split_lines (hit->second); + const auto lines = split_lines(hit->second); if (lines.size() != size_t(input_header.size(3))) - throw Exception ("Number of lines in header entry \"directions\" (" + str(lines.size()) + ") does not match number of volumes in image (" + str(input_header.size(3)) + ")"); + throw Exception("Number of lines in header entry \"directions\" (" + str(lines.size()) + + ") does not match number of volumes in image (" + str(input_header.size(3)) + ")"); Eigen::Matrix result; for (size_t l = 0; l != lines.size(); ++l) { - const auto v = parse_floats (lines[l]); + const auto v = parse_floats(lines[l]); if (!result.cols()) { if (!(v.size() == 2 || v.size() == 3)) - throw Exception ("Malformed \"directions\" field (expected matrix with 2 or 3 columns; data has " + str(v.size()) + " columns)"); - result.resize (lines.size(), v.size()); + throw Exception("Malformed \"directions\" field (expected matrix with 2 or 3 columns; data has " + + str(v.size()) + " columns)"); + result.resize(lines.size(), v.size()); } else { if (v.size() != size_t(result.cols())) - throw Exception ("Inconsistent number of columns in \"directions\" field"); + throw Exception("Inconsistent number of columns in \"directions\" field"); } if (result.cols() == 2) { - Eigen::Matrix azel (v.data()); + Eigen::Matrix azel(v.data()); Eigen::Vector3d dir; - Math::Sphere::spherical2cartesian (azel, dir); + Math::Sphere::spherical2cartesian(azel, dir); dir = rotation * dir; - Math::Sphere::cartesian2spherical (dir, azel); - result.row (l) = azel; + Math::Sphere::cartesian2spherical(dir, azel); + result.row(l) = azel; } else { - const Eigen::Vector3d dir = rotation * Eigen::Vector3d (v.data()); - result.row (l) = dir; + const Eigen::Vector3d dir = rotation * Eigen::Vector3d(v.data()); + result.row(l) = dir; } std::stringstream s; - Eigen::IOFormat format (6, Eigen::DontAlignCols, ",", "\n", "", "", "", ""); - s << result.format (format); + Eigen::IOFormat format(6, Eigen::DontAlignCols, ",", "\n", "", "", "", ""); + s << result.format(format); output_header.keyval()["directions"] = s.str(); } - } catch (Exception& e) { - e.display (2); - WARN ("Header entry \"directions\" not correctly reoriented"); + } catch (Exception &e) { + e.display(2); + WARN("Header entry \"directions\" not correctly reoriented"); } } } // Interpolator - int interp = 2; // cubic - opt = get_options ("interp"); + int interp = 2; // cubic + opt = get_options("interp"); if (opt.size()) { interp = opt[0][0]; if (!warp && !template_header) - WARN ("interpolator choice ignored since the input image will not be regridded"); + WARN("interpolator choice ignored since the input image will not be regridded"); } // over-sampling vector oversample = Adapter::AutoOverSample; - opt = get_options ("oversample"); + opt = get_options("oversample"); if (opt.size()) { if (!template_header.valid() && !warp) - throw Exception ("-oversample option applies only to regridding using the template option or to non-linear transformations"); - oversample = parse_ints (opt[0][0]); + throw Exception( + "-oversample option applies only to regridding using the template option or to non-linear transformations"); + oversample = parse_ints(opt[0][0]); if (oversample.size() == 1) - oversample.resize (3, oversample[0]); + oversample.resize(3, oversample[0]); else if (oversample.size() != 3) - throw Exception ("-oversample option requires either a single integer, or a comma-separated list of 3 integers"); + throw Exception("-oversample option requires either a single integer, or a comma-separated list of 3 integers"); for (const auto x : oversample) if (x < 1) - throw Exception ("-oversample factors must be positive integers"); + throw Exception("-oversample factors must be positive integers"); } else if (interp == 0) // default for nearest-neighbour is no oversampling - oversample = { 1, 1, 1 }; - - + oversample = {1, 1, 1}; // Out of bounds value float out_of_bounds_value = 0.0; - opt = get_options ("nan"); + opt = get_options("nan"); if (opt.size()) { out_of_bounds_value = NAN; if (!warp && !template_header) - WARN ("Out of bounds value ignored since the input image will not be regridded"); + WARN("Out of bounds value ignored since the input image will not be regridded"); } - auto input = input_header.get_image().with_direct_io (stride); + auto input = input_header.get_image().with_direct_io(stride); // Reslice the image onto template if (template_header.valid() && !warp) { - INFO ("image will be regridded"); + INFO("image will be regridded"); - if (get_options ("midway_space").size()) { + if (get_options("midway_space").size()) { INFO("regridding to midway space"); if (!half) - WARN ("regridding to midway_space assumes the linear transformation to be a transformation from input to midway space. Use -half if the input transformation is a full transformation."); + WARN("regridding to midway_space assumes the linear transformation to be a transformation from input to midway " + "space. Use -half if the input transformation is a full transformation."); transform_type linear_transform_inverse; linear_transform_inverse.matrix() = linear_transform.inverse().matrix(); - auto midway_header = compute_minimum_average_header (input_header, template_header, linear_transform_inverse, linear_transform); + auto midway_header = + compute_minimum_average_header(input_header, template_header, linear_transform_inverse, linear_transform); for (size_t i = 0; i < 3; ++i) { output_header.size(i) = midway_header.size(i); output_header.spacing(i) = midway_header.spacing(i); @@ -588,39 +600,40 @@ void run () } if (interp == 0) - output_header.datatype() = DataType::from_command_line (input_header.datatype()); - auto output = Image::create (argument[1], output_header).with_direct_io(); + output_header.datatype() = DataType::from_command_line(input_header.datatype()); + auto output = Image::create(argument[1], output_header).with_direct_io(); switch (interp) { - case 0: - Filter::reslice (input, output, linear_transform, oversample, out_of_bounds_value); - break; - case 1: - Filter::reslice (input, output, linear_transform, oversample, out_of_bounds_value); - break; - case 2: - Filter::reslice (input, output, linear_transform, oversample, out_of_bounds_value); - break; - case 3: - Filter::reslice (input, output, linear_transform, oversample, out_of_bounds_value); - break; - default: - assert (0); - break; + case 0: + Filter::reslice(input, output, linear_transform, oversample, out_of_bounds_value); + break; + case 1: + Filter::reslice(input, output, linear_transform, oversample, out_of_bounds_value); + break; + case 2: + Filter::reslice(input, output, linear_transform, oversample, out_of_bounds_value); + break; + case 3: + Filter::reslice(input, output, linear_transform, oversample, out_of_bounds_value); + break; + default: + assert(0); + break; } if (fod_reorientation) - Registration::Transform::reorient (reorient_msg.c_str(), output, output, linear_transform, directions_cartesian.transpose(), modulate_fod); + Registration::Transform::reorient( + reorient_msg.c_str(), output, output, linear_transform, directions_cartesian.transpose(), modulate_fod); if (modulate_jac) - apply_linear_jacobian (output, linear_transform); + apply_linear_jacobian(output, linear_transform); - DWI::export_grad_commandline (output); + DWI::export_grad_commandline(output); } else if (warp.valid()) { if (replace) - throw Exception ("you cannot use the -replace option with the -warp or -warp_df option"); + throw Exception("you cannot use the -replace option with the -warp or -warp_df option"); if (!template_header) { for (size_t i = 0; i < 3; ++i) { @@ -636,70 +649,69 @@ void run () Image warp_deform; // Warp to the midway space defined by the warp grid - if (get_options ("midway_space").size()) { - warp_deform = Registration::Warp::compute_midway_deformation (warp, from); - // Use the full transform to warp from the image image to the template + if (get_options("midway_space").size()) { + warp_deform = Registration::Warp::compute_midway_deformation(warp, from); + // Use the full transform to warp from the image image to the template } else { - warp_deform = Registration::Warp::compute_full_deformation (warp, template_header, from); + warp_deform = Registration::Warp::compute_full_deformation(warp, template_header, from); } - apply_warp (input, output, warp_deform, interp, out_of_bounds_value, oversample, modulate_jac); + apply_warp(input, output, warp_deform, interp, out_of_bounds_value, oversample, modulate_jac); if (fod_reorientation) - Registration::Transform::reorient_warp (reorient_msg.c_str(), - output, warp_deform, directions_cartesian.transpose(), modulate_fod); + Registration::Transform::reorient_warp( + reorient_msg.c_str(), output, warp_deform, directions_cartesian.transpose(), modulate_fod); - // Compose and apply input linear and 4D deformation field + // Compose and apply input linear and 4D deformation field } else if (warp.ndim() == 4 && linear) { - auto warp_composed = Image::scratch (warp); - Registration::Warp::compose_linear_deformation (linear_transform, warp, warp_composed); - apply_warp (input, output, warp_composed, interp, out_of_bounds_value, oversample, modulate_jac); + auto warp_composed = Image::scratch(warp); + Registration::Warp::compose_linear_deformation(linear_transform, warp, warp_composed); + apply_warp(input, output, warp_composed, interp, out_of_bounds_value, oversample, modulate_jac); if (fod_reorientation) - Registration::Transform::reorient_warp (reorient_msg.c_str(), - output, warp_composed, directions_cartesian.transpose(), modulate_fod); + Registration::Transform::reorient_warp( + reorient_msg.c_str(), output, warp_composed, directions_cartesian.transpose(), modulate_fod); - // Apply 4D deformation field only + // Apply 4D deformation field only } else { - apply_warp (input, output, warp, interp, out_of_bounds_value, oversample, modulate_jac); + apply_warp(input, output, warp, interp, out_of_bounds_value, oversample, modulate_jac); if (fod_reorientation) - Registration::Transform::reorient_warp (reorient_msg.c_str(), - output, warp, directions_cartesian.transpose(), modulate_fod); + Registration::Transform::reorient_warp( + reorient_msg.c_str(), output, warp, directions_cartesian.transpose(), modulate_fod); } - DWI::export_grad_commandline (output); + DWI::export_grad_commandline(output); - // No reslicing required, so just modify the header and do a straight copy of the data + // No reslicing required, so just modify the header and do a straight copy of the data } else if (linear || replace || axes.size()) { - if (get_options ("midway").size()) - throw Exception ("midway option given but no template image defined"); + if (get_options("midway").size()) + throw Exception("midway option given but no template image defined"); - INFO ("image will not be regridded"); + INFO("image will not be regridded"); Eigen::MatrixXd rotation = linear_transform.linear(); Eigen::MatrixXd temp = rotation.transpose() * rotation; - if (!temp.isIdentity (0.001)) + if (!temp.isIdentity(0.001)) WARN("the input linear transform is not orthonormal and therefore applying this without the -template " "option will mean the output header transform will also be not orthonormal"); - add_line (output_header.keyval()["comments"], std::string ("transform modified")); + add_line(output_header.keyval()["comments"], std::string("transform modified")); if (replace) output_header.transform() = linear_transform; else output_header.transform() = linear_transform.inverse() * output_header.transform(); - auto output = Image::create (argument[1], output_header).with_direct_io(); - copy_with_progress (input, output); + auto output = Image::create(argument[1], output_header).with_direct_io(); + copy_with_progress(input, output); if (fod_reorientation || modulate_jac) { transform_type transform = linear_transform; if (replace) transform = linear_transform * output_header.transform().inverse(); if (fod_reorientation) - Registration::Transform::reorient ("reorienting", output, output, transform, directions_cartesian.transpose()); + Registration::Transform::reorient("reorienting", output, output, transform, directions_cartesian.transpose()); if (modulate_jac) - apply_linear_jacobian (output, transform); + apply_linear_jacobian(output, transform); } - DWI::export_grad_commandline (output); + DWI::export_grad_commandline(output); } else { - throw Exception ("No operation specified"); + throw Exception("No operation specified"); } } - diff --git a/cmd/mrview.cpp b/cmd/mrview.cpp index d9b890a037..4b3e4b63ef 100644 --- a/cmd/mrview.cpp +++ b/cmd/mrview.cpp @@ -14,96 +14,79 @@ * For more details, see http://www.mrtrix.org/. */ -#include "gui/gui.h" #include "command.h" -#include "progressbar.h" -#include "memory.h" -#include "gui/mrview/icons.h" -#include "gui/mrview/window.h" +#include "gui/gui.h" #include "gui/mrview/file_open.h" +#include "gui/mrview/icons.h" #include "gui/mrview/mode/list.h" -#include "gui/mrview/tool/list.h" #include "gui/mrview/sync/syncmanager.h" - +#include "gui/mrview/tool/list.h" +#include "gui/mrview/window.h" +#include "memory.h" +#include "progressbar.h" using namespace MR; using namespace App; -void usage () -{ - AUTHOR = - "J-Donald Tournier (jdtournier@gmail.com), " - "Dave Raffelt (david.raffelt@florey.edu.au), " - "Robert E. Smith (robert.smith@florey.edu.au), " - "Rami Tabbara (rami.tabbara@florey.edu.au), " - "Max Pietsch (maximilian.pietsch@kcl.ac.uk), " - "Thijs Dhollander (thijs.dhollander@gmail.com)"; +void usage() { + AUTHOR = "J-Donald Tournier (jdtournier@gmail.com), " + "Dave Raffelt (david.raffelt@florey.edu.au), " + "Robert E. Smith (robert.smith@florey.edu.au), " + "Rami Tabbara (rami.tabbara@florey.edu.au), " + "Max Pietsch (maximilian.pietsch@kcl.ac.uk), " + "Thijs Dhollander (thijs.dhollander@gmail.com)"; SYNOPSIS = "The MRtrix image viewer"; DESCRIPTION - + "Any images listed as arguments will be loaded and available through the " - "image menu, with the first listed displayed initially. Any subsequent " - "command-line options will be processed as if the corresponding action had " - "been performed through the GUI." + +"Any images listed as arguments will be loaded and available through the " + "image menu, with the first listed displayed initially. Any subsequent " + "command-line options will be processed as if the corresponding action had " + "been performed through the GUI." - + "Note that because images loaded as arguments (i.e. simply listed on the " - "command-line) are opened before the GUI is shown, subsequent actions to be " - "performed via the various command-line options must appear after the last " - "argument. This is to avoid confusion about which option will apply to which " - "image. If you need fine control over this, please use the -load or -select_image " - "options. For example:" + + "Note that because images loaded as arguments (i.e. simply listed on the " + "command-line) are opened before the GUI is shown, subsequent actions to be " + "performed via the various command-line options must appear after the last " + "argument. This is to avoid confusion about which option will apply to which " + "image. If you need fine control over this, please use the -load or -select_image " + "options. For example:" - + "$ mrview -load image1.mif -interpolation 0 -load image2.mif -interpolation 0" + + "$ mrview -load image1.mif -interpolation 0 -load image2.mif -interpolation 0" - + "or" + + "or" - + "$ mrview image1.mif image2.mif -interpolation 0 -select_image 2 -interpolation 0"; + + "$ mrview image1.mif image2.mif -interpolation 0 -select_image 2 -interpolation 0"; REFERENCES - + "Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal - "MRtrix: Diffusion tractography in crossing fiber regions. " - "Int. J. Imaging Syst. Technol., 2012, 22, 53-66"; + +"Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal + "MRtrix: Diffusion tractography in crossing fiber regions. " + "Int. J. Imaging Syst. Technol., 2012, 22, 53-66"; ARGUMENTS - + Argument ("image", "An image to be loaded.") - .optional() - .allow_multiple() - .type_image_in (); + +Argument("image", "An image to be loaded.").optional().allow_multiple().type_image_in(); - GUI::MRView::Window::add_commandline_options (OPTIONS); + GUI::MRView::Window::add_commandline_options(OPTIONS); -#define TOOL(classname, name, description) \ - MR::GUI::MRView::Tool::classname::add_commandline_options (OPTIONS); +#define TOOL(classname, name, description) MR::GUI::MRView::Tool::classname::add_commandline_options(OPTIONS); { using namespace MR::GUI::MRView::Tool; #include "gui/mrview/tool/list.h" } REQUIRES_AT_LEAST_ONE_ARGUMENT = false; - } - - - -void run () -{ +void run() { GUI::MRView::Window window; - MR::GUI::MRView::Sync::SyncManager sync;//sync allows syncing between mrview windows in different processes + MR::GUI::MRView::Sync::SyncManager sync; // sync allows syncing between mrview windows in different processes window.show(); try { window.parse_arguments(); - } - catch (Exception& e) { + } catch (Exception &e) { e.display(); return; } if (qApp->exec()) - throw Exception ("error running Qt application"); + throw Exception("error running Qt application"); } - - - - diff --git a/cmd/mtnormalise.cpp b/cmd/mtnormalise.cpp index 9284bacf4f..f25b57ac4f 100644 --- a/cmd/mtnormalise.cpp +++ b/cmd/mtnormalise.cpp @@ -14,13 +14,13 @@ * For more details, see http://www.mrtrix.org/. */ +#include "adapter/replicate.h" +#include "algo/loop.h" +#include "algo/threaded_copy.h" #include "command.h" #include "image.h" -#include "algo/loop.h" -#include "transform.h" #include "math/least_squares.h" -#include "algo/threaded_copy.h" -#include "adapter/replicate.h" +#include "transform.h" using namespace MR; using namespace App; @@ -30,86 +30,98 @@ using namespace App; #define DEFAULT_BALANCE_MAXITER_VALUE 7 #define DEFAULT_POLY_ORDER 3 -const char* poly_order_choices[] = { "0", "1", "2", "3", nullptr }; +const char *poly_order_choices[] = {"0", "1", "2", "3", nullptr}; -void usage () -{ - AUTHOR = "Thijs Dhollander (thijs.dhollander@gmail.com), Rami Tabbara (rami.tabbara@florey.edu.au), " - "David Raffelt (david.raffelt@florey.edu.au), Jonas Rosnarho-Tornstrand (jonas.rosnarho-tornstrand@kcl.ac.uk) " - "and J-Donald Tournier (jdtournier@gmail.com)"; +void usage() { + AUTHOR = + "Thijs Dhollander (thijs.dhollander@gmail.com), Rami Tabbara (rami.tabbara@florey.edu.au), " + "David Raffelt (david.raffelt@florey.edu.au), Jonas Rosnarho-Tornstrand (jonas.rosnarho-tornstrand@kcl.ac.uk) " + "and J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Multi-tissue informed log-domain intensity normalisation"; DESCRIPTION - + "This command takes as input any number of tissue components (e.g. from " - "multi-tissue CSD) and outputs corresponding normalised tissue components " - "corrected for the effects of (residual) intensity inhomogeneities. " - "Intensity normalisation is performed by optimising the voxel-wise sum of " - "all tissue compartments towards a constant value, under constraints of " - "spatial smoothness (polynomial basis of a given order). Different to " - "the Raffelt et al. 2017 abstract, this algorithm performs this task " - "in the log-domain instead, with added gradual outlier rejection, different " - "handling of the balancing factors between tissue compartments and a " - "different iteration structure." - - + "The -mask option is mandatory and is optimally provided with a brain mask " - "(such as the one obtained from dwi2mask earlier in the processing pipeline). " - "Outlier areas with exceptionally low or high combined tissue contributions are " - "accounted for and reoptimised as the intensity inhomogeneity estimation becomes " - "more accurate."; + +"This command takes as input any number of tissue components (e.g. from " + "multi-tissue CSD) and outputs corresponding normalised tissue components " + "corrected for the effects of (residual) intensity inhomogeneities. " + "Intensity normalisation is performed by optimising the voxel-wise sum of " + "all tissue compartments towards a constant value, under constraints of " + "spatial smoothness (polynomial basis of a given order). Different to " + "the Raffelt et al. 2017 abstract, this algorithm performs this task " + "in the log-domain instead, with added gradual outlier rejection, different " + "handling of the balancing factors between tissue compartments and a " + "different iteration structure." + + + "The -mask option is mandatory and is optimally provided with a brain mask " + "(such as the one obtained from dwi2mask earlier in the processing pipeline). " + "Outlier areas with exceptionally low or high combined tissue contributions are " + "accounted for and reoptimised as the intensity inhomogeneity estimation becomes " + "more accurate."; EXAMPLES - + Example ("Default usage (for 3-tissue CSD compartments)", - "mtnormalise wmfod.mif wmfod_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif", - "Note how for each tissue compartment, the input and output images are provided as " - "a consecutive pair."); + +Example("Default usage (for 3-tissue CSD compartments)", + "mtnormalise wmfod.mif wmfod_norm.mif gm.mif gm_norm.mif csf.mif csf_norm.mif -mask mask.mif", + "Note how for each tissue compartment, the input and output images are provided as " + "a consecutive pair."); ARGUMENTS - + Argument ("input output", "list of all input and output tissue compartment files (see example usage).").type_various().allow_multiple(); + +Argument("input output", "list of all input and output tissue compartment files (see example usage).") + .type_various() + .allow_multiple(); OPTIONS - + Option ("mask", "the mask defines the data used to compute the intensity normalisation. This option is mandatory.").required () - + Argument ("image").type_image_in () - - + Option ("order", "the maximum order of the polynomial basis used to fit the normalisation field in the log-domain. " - "An order of 0 is equivalent to not allowing spatial variance of the intensity normalisation factor. " - "(default: " + str(DEFAULT_POLY_ORDER) + ")") - + Argument ("number").type_choice (poly_order_choices) - - + Option ("niter", "set the number of iterations. The first (and potentially only) entry applies to the main loop. " - "If supplied as a comma-separated list of integers, the second entry applies to the inner loop to update the balance factors " - "(default: " + str(DEFAULT_MAIN_ITER_VALUE) + "," + str(DEFAULT_BALANCE_MAXITER_VALUE) + ").") - + Argument ("number").type_sequence_int() - - + Option ("reference", "specify the (positive) reference value to which the summed tissue compartments will be normalised. " - "(default: " + str(DEFAULT_REFERENCE_VALUE, 6) + ", SH DC term for unit angular integral)") - + Argument ("number").type_float (std::numeric_limits::min()) - - + Option ("balanced", "incorporate the per-tissue balancing factors into scaling of the output images " - "(NOTE: use of this option has critical consequences for AFD intensity normalisation; " - "should not be used unless these consequences are fully understood)") - - + OptionGroup ("Debugging options") - - + Option ("check_norm", "output the final estimated spatially varying intensity level that is used for normalisation.") - + Argument ("image").type_image_out () - - + Option ("check_mask", "output the final mask used to compute the normalisation. " - "This mask excludes regions identified as outliers by the optimisation process.") - + Argument ("image").type_image_out () - - + Option ("check_factors", "output the tissue balance factors computed during normalisation.") - + Argument ("file").type_file_out (); - - REFERENCES - + "Raffelt, D.; Dhollander, T.; Tournier, J.-D.; Tabbara, R.; Smith, R. E.; Pierre, E. & Connelly, A. " // Internal - "Bias Field Correction and Intensity Normalisation for Quantitative Analysis of Apparent Fibre Density. " - "In Proc. ISMRM, 2017, 26, 3541" - + "Dhollander, T.; Tabbara, R.; Rosnarho-Tornstrand, J.; Tournier, J.-D.; Raffelt, D. & Connelly, A. " // Internal - "Multi-tissue log-domain intensity and inhomogeneity normalisation for quantitative apparent fibre density. " - "In Proc. ISMRM, 2021, 29, 2472"; -; - + +Option("mask", "the mask defines the data used to compute the intensity normalisation. This option is mandatory.") + .required() + + Argument("image").type_image_in() + + + Option("order", + "the maximum order of the polynomial basis used to fit the normalisation field in the log-domain. " + "An order of 0 is equivalent to not allowing spatial variance of the intensity normalisation factor. " + "(default: " + + str(DEFAULT_POLY_ORDER) + ")") + + Argument("number").type_choice(poly_order_choices) + + + Option("niter", + "set the number of iterations. The first (and potentially only) entry applies to the main loop. " + "If supplied as a comma-separated list of integers, the second entry applies to the inner loop to " + "update the balance factors " + "(default: " + + str(DEFAULT_MAIN_ITER_VALUE) + "," + str(DEFAULT_BALANCE_MAXITER_VALUE) + ").") + + Argument("number").type_sequence_int() + + + Option("reference", + "specify the (positive) reference value to which the summed tissue compartments will be normalised. " + "(default: " + + str(DEFAULT_REFERENCE_VALUE, 6) + ", SH DC term for unit angular integral)") + + Argument("number").type_float(std::numeric_limits::min()) + + + Option("balanced", + "incorporate the per-tissue balancing factors into scaling of the output images " + "(NOTE: use of this option has critical consequences for AFD intensity normalisation; " + "should not be used unless these consequences are fully understood)") + + + OptionGroup("Debugging options") + + + Option("check_norm", + "output the final estimated spatially varying intensity level that is used for normalisation.") + + Argument("image").type_image_out() + + + Option("check_mask", + "output the final mask used to compute the normalisation. " + "This mask excludes regions identified as outliers by the optimisation process.") + + Argument("image").type_image_out() + + + Option("check_factors", "output the tissue balance factors computed during normalisation.") + + Argument("file").type_file_out(); + + REFERENCES + +"Raffelt, D.; Dhollander, T.; Tournier, J.-D.; Tabbara, R.; Smith, R. E.; Pierre, E. & Connelly, A. " // Internal + "Bias Field Correction and Intensity Normalisation for Quantitative Analysis of Apparent Fibre Density. " + "In Proc. ISMRM, 2017, 26, 3541" + + "Dhollander, T.; Tabbara, R.; Rosnarho-Tornstrand, J.; Tournier, J.-D.; Raffelt, D. & Connelly, A. " // Internal + "Multi-tissue log-domain intensity and inhomogeneity normalisation for quantitative apparent fibre density. " + "In Proc. ISMRM, 2021, 29, 2472"; + ; } using ValueType = float; @@ -118,34 +130,32 @@ using MaskType = Image; using IndexType = Image; // Function to get the number of basis vectors based on the desired order -int num_basis_vec_for_order (int order) -{ +int num_basis_vec_for_order(int order) { switch (order) { - case 0: return 1; - case 1: return 4; - case 2: return 10; - default: return 20; + case 0: + return 1; + case 1: + return 4; + case 2: + return 10; + default: + return 20; } - assert (false); + assert(false); return -1; }; - - - - // Struct to get user specified number of basis functions -struct PolyBasisFunction { - PolyBasisFunction(const int order) : - n_basis_vecs (num_basis_vec_for_order (order)) { }; +struct PolyBasisFunction { + PolyBasisFunction(const int order) : n_basis_vecs(num_basis_vec_for_order(order)){}; const int n_basis_vecs; - FORCE_INLINE Eigen::VectorXd operator () (const Eigen::Vector3d& pos) const { + FORCE_INLINE Eigen::VectorXd operator()(const Eigen::Vector3d &pos) const { double x = pos[0]; double y = pos[1]; double z = pos[2]; - Eigen::VectorXd basis (n_basis_vecs); + Eigen::VectorXd basis(n_basis_vecs); basis(0) = 1.0; if (n_basis_vecs < 4) return basis; @@ -179,29 +189,21 @@ struct PolyBasisFunction { } }; +IndexType index_mask_voxels(size_t &num_voxels) { + auto opt = get_options("mask"); + auto mask = MaskType::open(opt[0][0]); + check_effective_dimensionality(mask, 3); - - - - - - -IndexType index_mask_voxels (size_t& num_voxels) -{ - auto opt = get_options ("mask"); - auto mask = MaskType::open (opt[0][0]); - check_effective_dimensionality (mask, 3); - - if (voxel_count (mask, 0, 3) >= std::numeric_limits::max()-1) - throw Exception ("mask size exceeds maximum supported using 32-bit integer"); + if (voxel_count(mask, 0, 3) >= std::numeric_limits::max() - 1) + throw Exception("mask size exceeds maximum supported using 32-bit integer"); num_voxels = 0; - Header header (mask); + Header header(mask); header.ndim() = 3; header.datatype() = DataType::UInt32; - IndexType index = IndexType::scratch (header, "index"); + IndexType index = IndexType::scratch(header, "index"); - for (auto l = Loop(0, 3) (mask, index); l; ++l) { + for (auto l = Loop(0, 3)(mask, index); l; ++l) { if (mask.value()) index.value() = num_voxels++; else @@ -209,121 +211,107 @@ IndexType index_mask_voxels (size_t& num_voxels) } if (!num_voxels) - throw Exception ("Mask contains no valid voxels."); + throw Exception("Mask contains no valid voxels."); - INFO ("mask image contains " + str(num_voxels) + " voxels"); + INFO("mask image contains " + str(num_voxels) + " voxels"); return index; } +Eigen::MatrixXd initialise_basis(IndexType &index, size_t num_voxels, int order) { + struct BasisInitialiser { + BasisInitialiser(const Transform &transform, const PolyBasisFunction &basis_function, Eigen::MatrixXd &basis) + : basis_function(basis_function), transform(transform), basis(basis) {} - - -Eigen::MatrixXd initialise_basis (IndexType& index, size_t num_voxels, int order) -{ - struct BasisInitialiser { - BasisInitialiser (const Transform& transform, const PolyBasisFunction& basis_function, Eigen::MatrixXd& basis) : - basis_function (basis_function), - transform (transform), - basis (basis) { } - - void operator() (IndexType& index) { + void operator()(IndexType &index) { const uint32_t idx = index.value(); if (idx != std::numeric_limits::max()) { - assert (idx < basis.rows()); - Eigen::Vector3d vox (index.index(0), index.index(1), index.index(2)); + assert(idx < basis.rows()); + Eigen::Vector3d vox(index.index(0), index.index(1), index.index(2)); Eigen::Vector3d pos = transform.voxel2scanner * vox; - basis.row(idx) = basis_function (pos); + basis.row(idx) = basis_function(pos); } } - const PolyBasisFunction& basis_function; - const Transform& transform; - Eigen::MatrixXd& basis; + const PolyBasisFunction &basis_function; + const Transform &transform; + Eigen::MatrixXd &basis; }; - INFO ("initialising basis..."); + INFO("initialising basis..."); - PolyBasisFunction basis_function (order); - Transform transform (index); - Eigen::MatrixXd basis (num_voxels, num_basis_vec_for_order (order)); + PolyBasisFunction basis_function(order); + Transform transform(index); + Eigen::MatrixXd basis(num_voxels, num_basis_vec_for_order(order)); - ThreadedLoop (index, 0, 3, 2).run (BasisInitialiser (transform, basis_function, basis), index); + ThreadedLoop(index, 0, 3, 2).run(BasisInitialiser(transform, basis_function, basis), index); return basis; } - - - - - -void load_data (Eigen::MatrixXd& data, const std::string& image_name, IndexType& index) -{ +void load_data(Eigen::MatrixXd &data, const std::string &image_name, IndexType &index) { static int num = 0; - auto in = ImageType::open (image_name); - check_dimensions (index, in, 0, 3); + auto in = ImageType::open(image_name); + check_dimensions(index, in, 0, 3); - struct Loader { - public: - Loader (Eigen::MatrixXd& data, int num) : data (data), num (num) { } + struct Loader { + public: + Loader(Eigen::MatrixXd &data, int num) : data(data), num(num) {} - void operator() (ImageType& in, IndexType& index) { - const uint32_t idx = index.value(); - if (idx != std::numeric_limits::max()) - data(idx, num) = std::max (in.value(), 0.0); - } - Eigen::MatrixXd& data; - const int num; + void operator()(ImageType &in, IndexType &index) { + const uint32_t idx = index.value(); + if (idx != std::numeric_limits::max()) + data(idx, num) = std::max(in.value(), 0.0); + } + Eigen::MatrixXd &data; + const int num; }; - ThreadedLoop (in, 0, 3, 2).run (Loader (data, num), in, index); + ThreadedLoop(in, 0, 3, 2).run(Loader(data, num), in, index); ++num; } - - -inline bool lessthan_NaN (const double& a, const double& b) { - if (std::isnan (a)) +inline bool lessthan_NaN(const double &a, const double &b) { + if (std::isnan(a)) return true; - if (std::isnan (b)) + if (std::isnan(b)) return false; - return a= lower_outlier_threshold && v <= upper_outlier_threshold; + double operator()(double v, double w) const { + v = std::isfinite(v) && v >= lower_outlier_threshold && v <= upper_outlier_threshold; if (v != w) ++changed; return v; @@ -331,194 +319,166 @@ size_t detect_outliers ( }; size_t changed = 0; - SetWeight set_weight = { changed, lower_outlier_threshold, upper_outlier_threshold }; - weights = summed_log.binaryExpr (weights, set_weight); + SetWeight set_weight = {changed, lower_outlier_threshold, upper_outlier_threshold}; + weights = summed_log.binaryExpr(weights, set_weight); return changed; } - - - - - - -void compute_balance_factors ( - const Eigen::MatrixXd& data, - const Eigen::VectorXd& field, - const Eigen::VectorXd& weights, - Eigen::VectorXd& balance_factors) -{ +void compute_balance_factors(const Eigen::MatrixXd &data, + const Eigen::VectorXd &field, + const Eigen::VectorXd &weights, + Eigen::VectorXd &balance_factors) { Eigen::MatrixXd scaled_data = data.transpose().array().rowwise() / field.transpose().array(); for (ssize_t n = 0; n < scaled_data.cols(); ++n) { if (!weights[n]) scaled_data.col(n).array() = 0.0; } - Eigen::MatrixXd HtH (data.cols(), data.cols()); + Eigen::MatrixXd HtH(data.cols(), data.cols()); HtH.triangularView() = scaled_data * scaled_data.transpose(); Eigen::LLT llt; - balance_factors.noalias() = llt.compute (HtH.triangularView()).solve (scaled_data * Eigen::VectorXd::Ones(field.size())); + balance_factors.noalias() = + llt.compute(HtH.triangularView()).solve(scaled_data * Eigen::VectorXd::Ones(field.size())); // Ensure our balance factors satisfy the condition that sum(log(balance_factors)) = 0 if (!balance_factors.allFinite() || (balance_factors.array() <= 0.0).any()) - throw Exception ("Non-positive tissue balance factor was computed." - " Balance factors: " + str(balance_factors.transpose())); + throw Exception("Non-positive tissue balance factor was computed." + " Balance factors: " + + str(balance_factors.transpose())); - balance_factors /= std::exp (balance_factors.array().log().sum() / data.cols()); + balance_factors /= std::exp(balance_factors.array().log().sum() / data.cols()); } - - - -void update_field ( - const double log_norm_value, - const Eigen::MatrixXd& basis, - const Eigen::MatrixXd& data, - const Eigen::VectorXd& balance_factors, - const Eigen::VectorXd& weights, - Eigen::VectorXd& field_coeffs, - Eigen::VectorXd& field) -{ - struct LogWeight { - double operator() (double sum, double weight) const { +void update_field(const double log_norm_value, + const Eigen::MatrixXd &basis, + const Eigen::MatrixXd &data, + const Eigen::VectorXd &balance_factors, + const Eigen::VectorXd &weights, + Eigen::VectorXd &field_coeffs, + Eigen::VectorXd &field) { + struct LogWeight { + double operator()(double sum, double weight) const { return sum > 0.0 ? weight * (std::log(sum) - log_norm_value) : 0.0; } const double log_norm_value; }; - LogWeight logweight = { log_norm_value }; + LogWeight logweight = {log_norm_value}; Eigen::VectorXd logsum = data * balance_factors; - logsum = logsum.binaryExpr (weights, logweight); + logsum = logsum.binaryExpr(weights, logweight); - Eigen::MatrixXd HtH = Eigen::MatrixXd::Zero (basis.cols(), basis.cols()); - HtH.selfadjointView().rankUpdate ((basis.transpose().array().rowwise() * weights.transpose().array()).matrix()); + Eigen::MatrixXd HtH = Eigen::MatrixXd::Zero(basis.cols(), basis.cols()); + HtH.selfadjointView().rankUpdate( + (basis.transpose().array().rowwise() * weights.transpose().array()).matrix()); Eigen::LLT llt; - field_coeffs.noalias() = llt.compute (HtH.selfadjointView()).solve (basis.transpose() * logsum); + field_coeffs.noalias() = llt.compute(HtH.selfadjointView()).solve(basis.transpose() * logsum); field.noalias() = (basis * field_coeffs).array().exp().matrix(); } - - - - -ImageType compute_full_field (int order, const Eigen::VectorXd& field_coeffs, const IndexType& index) -{ - Header header (index); +ImageType compute_full_field(int order, const Eigen::VectorXd &field_coeffs, const IndexType &index) { + Header header(index); header.datatype() = DataType::Float32; - auto out = ImageType::scratch (header, "full field"); - Transform transform (out); + auto out = ImageType::scratch(header, "full field"); + Transform transform(out); - struct FieldWriter { - void operator() (ImageType& field) const { - Eigen::Vector3d vox (field.index(0), field.index(1), field.index(2)); + struct FieldWriter { + void operator()(ImageType &field) const { + Eigen::Vector3d vox(field.index(0), field.index(1), field.index(2)); Eigen::Vector3d pos = transform.voxel2scanner * vox; - field.value() = std::exp (basis_function (pos).dot (field_coeffs)); + field.value() = std::exp(basis_function(pos).dot(field_coeffs)); } - const PolyBasisFunction& basis_function; - const Eigen::VectorXd& field_coeffs; - const Transform& transform; + const PolyBasisFunction &basis_function; + const Eigen::VectorXd &field_coeffs; + const Transform &transform; }; - PolyBasisFunction basis_function (order); + PolyBasisFunction basis_function(order); - FieldWriter writer = { basis_function, field_coeffs, transform }; + FieldWriter writer = {basis_function, field_coeffs, transform}; - ThreadedLoop (out, 0, 3).run (writer, out); + ThreadedLoop(out, 0, 3).run(writer, out); return out; } - - - - -void write_weights (const Eigen::VectorXd& data, IndexType& index, const std::string& output_file_name) -{ - Header header (index); +void write_weights(const Eigen::VectorXd &data, IndexType &index, const std::string &output_file_name) { + Header header(index); header.datatype() = DataType::Float32; - auto out = ImageType::create (output_file_name, header); + auto out = ImageType::create(output_file_name, header); - struct Write { - void operator() (ImageType& out, IndexType& index) const { + struct Write { + void operator()(ImageType &out, IndexType &index) const { const uint32_t idx = index.value(); if (idx != std::numeric_limits::max()) { out.value() = data[idx]; } } - const Eigen::VectorXd& data; - } write = { data }; + const Eigen::VectorXd &data; + } write = {data}; - ThreadedLoop (index, 0, 3).run (write, out, index); + ThreadedLoop(index, 0, 3).run(write, out, index); } - - -void write_output ( - const std::string& original, - const std::string& corrected, - bool output_balanced, - double balance_factor, - ImageType& field, - double lognorm_scale) -{ +void write_output(const std::string &original, + const std::string &corrected, + bool output_balanced, + double balance_factor, + ImageType &field, + double lognorm_scale) { using ReplicatorType = Adapter::Replicate; - struct Scaler { - void operator() (ImageType& original, ImageType& corrected, ReplicatorType& field) const { + struct Scaler { + void operator()(ImageType &original, ImageType &corrected, ReplicatorType &field) const { corrected.value() = balance_factor * original.value() / field.value(); } const double balance_factor; }; - auto in = ImageType::open (original); - Header header (in); + auto in = ImageType::open(original); + Header header(in); header.datatype() = DataType::Float32; header.keyval()["lognorm_scale"] = str(lognorm_scale); if (output_balanced) header.keyval()["lognorm_balance"] = str(balance_factor); else balance_factor = 1.0; - auto out = ImageType::create (corrected, header); + auto out = ImageType::create(corrected, header); - Header header_broadcast (field); + Header header_broadcast(field); header_broadcast.ndim() = 4; header_broadcast.size(3) = in.ndim() > 3 ? in.size(3) : 1; - ReplicatorType field_broadcast (field, header_broadcast); + ReplicatorType field_broadcast(field, header_broadcast); - Scaler scaler = { balance_factor }; - ThreadedLoop (in).run (scaler, in, out, field_broadcast); + Scaler scaler = {balance_factor}; + ThreadedLoop(in).run(scaler, in, out, field_broadcast); } - - - - -void run () -{ +void run() { if (argument.size() % 2) - throw Exception ("The number of arguments must be even, provided as pairs of each input and its corresponding output file."); + throw Exception( + "The number of arguments must be even, provided as pairs of each input and its corresponding output file."); if (argument.size() == 2) WARN("Only one contrast provided. If multi-tissue CSD was performed, provide all components to mtnormalise."); - const int order = get_option_value ("order", DEFAULT_POLY_ORDER); - const float reference_value = get_option_value ("reference", DEFAULT_REFERENCE_VALUE); - const float log_ref_value = std::log (reference_value); + const int order = get_option_value("order", DEFAULT_POLY_ORDER); + const float reference_value = get_option_value("reference", DEFAULT_REFERENCE_VALUE); + const float log_ref_value = std::log(reference_value); size_t max_iter = DEFAULT_MAIN_ITER_VALUE; size_t max_balance_iter = DEFAULT_BALANCE_MAXITER_VALUE; - auto opt = get_options ("niter"); + auto opt = get_options("niter"); if (opt.size()) { - vector num = parse_ints (opt[0][0]); + vector num = parse_ints(opt[0][0]); if (num.size() < 1 && num.size() > 2) - throw Exception ("unexpected number of entries provided to option \"-niter\""); + throw Exception("unexpected number of entries provided to option \"-niter\""); for (auto n : num) if (!n) - throw Exception ("number of iterations must be nonzero"); + throw Exception("number of iterations must be nonzero"); max_iter = num[0]; if (num.size() > 1) @@ -526,92 +486,89 @@ void run () } // Setting the n_tissue_types - const size_t n_tissue_types = argument.size()/2; + const size_t n_tissue_types = argument.size() / 2; size_t num_voxels; - auto index = index_mask_voxels (num_voxels); + auto index = index_mask_voxels(num_voxels); - Eigen::MatrixXd data (num_voxels, n_tissue_types); + Eigen::MatrixXd data(num_voxels, n_tissue_types); for (size_t n = 0; n < n_tissue_types; ++n) { - if (Path::exists (argument[2*n+1]) && !App::overwrite_files) - throw Exception ("Output file \"" + argument[2*n+1] + "\" already exists. (use -force option to force overwrite)"); - load_data (data, argument[2*n], index); + if (Path::exists(argument[2 * n + 1]) && !App::overwrite_files) + throw Exception("Output file \"" + argument[2 * n + 1] + + "\" already exists. (use -force option to force overwrite)"); + load_data(data, argument[2 * n], index); } size_t num_non_finite = (!data.array().isFinite()).count(); if (num_non_finite > 0) { - WARN ("Input data contain " + str(num_non_finite) + " non-finite voxel" + ( num_non_finite > 1 ? "s" : "" )); - WARN (" Results may be affected if the data contain many non-finite values"); - WARN (" Please refine your mask to avoid non-finite values if this is a problem"); + WARN("Input data contain " + str(num_non_finite) + " non-finite voxel" + (num_non_finite > 1 ? "s" : "")); + WARN(" Results may be affected if the data contain many non-finite values"); + WARN(" Please refine your mask to avoid non-finite values if this is a problem"); } - auto basis = initialise_basis (index, num_voxels, order); - - struct finite_and_positive { double operator() (double v) const { return std::isfinite(v) && v > 0.0; } }; - Eigen::VectorXd weights = data.rowwise().sum().unaryExpr (finite_and_positive()); + auto basis = initialise_basis(index, num_voxels, order); - Eigen::VectorXd field = Eigen::VectorXd::Ones (num_voxels); - Eigen::VectorXd field_coeffs (basis.cols()); - Eigen::VectorXd balance_factors (Eigen::VectorXd::Ones (n_tissue_types)); + struct finite_and_positive { + double operator()(double v) const { return std::isfinite(v) && v > 0.0; } + }; + Eigen::VectorXd weights = data.rowwise().sum().unaryExpr(finite_and_positive()); + Eigen::VectorXd field = Eigen::VectorXd::Ones(num_voxels); + Eigen::VectorXd field_coeffs(basis.cols()); + Eigen::VectorXd balance_factors(Eigen::VectorXd::Ones(n_tissue_types)); { size_t iter = 0; - ProgressBar progress ("performing log-domain intensity normalisation", max_iter); + ProgressBar progress("performing log-domain intensity normalisation", max_iter); - size_t outliers_changed = detect_outliers (3.0, data, field, balance_factors, weights); + size_t outliers_changed = detect_outliers(3.0, data, field, balance_factors, weights); while (++iter <= max_iter) { - INFO ("Iteration: " + str(iter)); + INFO("Iteration: " + str(iter)); size_t balance_iter = 1; // Iteratively compute tissue balance factors with outlier rejection do { - DEBUG ("Balance and outlier rejection iteration " + str(balance_iter) + " starts."); + DEBUG("Balance and outlier rejection iteration " + str(balance_iter) + " starts."); if (n_tissue_types > 1) { - compute_balance_factors (data, field, weights, balance_factors); - INFO (" balance factors (" + str(balance_iter) + "): " + str(balance_factors.transpose())); + compute_balance_factors(data, field, weights, balance_factors); + INFO(" balance factors (" + str(balance_iter) + "): " + str(balance_factors.transpose())); } - outliers_changed = detect_outliers (1.5, data, field, balance_factors, weights); + outliers_changed = detect_outliers(1.5, data, field, balance_factors, weights); } while (outliers_changed && balance_iter++ < max_balance_iter); - - update_field (log_ref_value, basis, data, balance_factors, weights, field_coeffs, field); + update_field(log_ref_value, basis, data, balance_factors, weights, field_coeffs, field); progress++; } - } + auto full_field = compute_full_field(order, field_coeffs, index); - - auto full_field = compute_full_field (order, field_coeffs, index); - - opt = get_options ("check_norm"); + opt = get_options("check_norm"); if (opt.size()) { - auto out = ImageType::create (opt[0][0], full_field); - threaded_copy (full_field, out); + auto out = ImageType::create(opt[0][0], full_field); + threaded_copy(full_field, out); } - opt = get_options ("check_mask"); + opt = get_options("check_mask"); if (opt.size()) - write_weights (weights, index, opt[0][0]); + write_weights(weights, index, opt[0][0]); - opt = get_options ("check_factors"); + opt = get_options("check_factors"); if (opt.size()) { - File::OFStream factors_output (opt[0][0]); + File::OFStream factors_output(opt[0][0]); factors_output << balance_factors.transpose() << "\n"; } - double lognorm_scale = std::exp ((field.array().log() * weights.array()).sum() / weights.sum()); + double lognorm_scale = std::exp((field.array().log() * weights.array()).sum() / weights.sum()); const bool output_balanced = get_options("balanced").size(); for (size_t n = 0; n < n_tissue_types; ++n) - write_output (argument[2*n], argument[2*n+1], output_balanced, balance_factors[n], full_field, lognorm_scale); + write_output(argument[2 * n], argument[2 * n + 1], output_balanced, balance_factors[n], full_field, lognorm_scale); } - diff --git a/cmd/peaks2amp.cpp b/cmd/peaks2amp.cpp index b1b3ee1d04..2b91b825a6 100644 --- a/cmd/peaks2amp.cpp +++ b/cmd/peaks2amp.cpp @@ -14,55 +14,54 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "image.h" #include "algo/loop.h" +#include "command.h" #include "fixel/helpers.h" - +#include "image.h" using namespace MR; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Extract amplitudes from a peak directions image"; ARGUMENTS - + Argument ("directions", "the input directions image. Each volume corresponds to the x, y & z " - "component of each direction vector in turn.").type_image_in () + +Argument("directions", + "the input directions image. Each volume corresponds to the x, y & z " + "component of each direction vector in turn.") + .type_image_in() - + Argument ("amplitudes", "the output amplitudes image.").type_image_out (); + + Argument("amplitudes", "the output amplitudes image.").type_image_out(); } - - -void run () -{ - Header H_in = Header::open (argument[0]); - Peaks::check (H_in); +void run() { + Header H_in = Header::open(argument[0]); + Peaks::check(H_in); auto dir = H_in.get_image(); - Header header (dir); - header.size(3) = header.size(3)/3; + Header header(dir); + header.size(3) = header.size(3) / 3; - auto amp = Image::create (argument[1], header); + auto amp = Image::create(argument[1], header); auto loop = Loop("converting directions to amplitudes", 0, 3); - for (auto i = loop (dir, amp); i; ++i) { + for (auto i = loop(dir, amp); i; ++i) { Eigen::Vector3f n; dir.index(3) = 0; amp.index(3) = 0; while (dir.index(3) < dir.size(3)) { - n[0] = dir.value(); ++dir.index(3); - n[1] = dir.value(); ++dir.index(3); - n[2] = dir.value(); ++dir.index(3); + n[0] = dir.value(); + ++dir.index(3); + n[1] = dir.value(); + ++dir.index(3); + n[2] = dir.value(); + ++dir.index(3); float amplitude = 0.0; - if (std::isfinite (n[0]) && std::isfinite (n[1]) && std::isfinite (n[2])) + if (std::isfinite(n[0]) && std::isfinite(n[1]) && std::isfinite(n[2])) amplitude = n.norm(); amp.value() = amplitude; diff --git a/cmd/peaks2fixel.cpp b/cmd/peaks2fixel.cpp index 621917e8ff..c3383b44da 100644 --- a/cmd/peaks2fixel.cpp +++ b/cmd/peaks2fixel.cpp @@ -14,43 +14,36 @@ * For more details, see http://www.mrtrix.org/. */ - -#include "command.h" -#include "image.h" #include "algo/loop.h" +#include "command.h" #include "fixel/fixel.h" #include "fixel/helpers.h" - +#include "image.h" using namespace MR; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Convert peak directions image to a fixel directory"; DESCRIPTION - + Fixel::format_description; + +Fixel::format_description; ARGUMENTS - + Argument ("directions", "the input directions image; each volume corresponds to the x, y & z " - "component of each direction vector in turn.").type_image_in() + +Argument("directions", + "the input directions image; each volume corresponds to the x, y & z " + "component of each direction vector in turn.") + .type_image_in() - + Argument ("fixels", "the output fixel directory.").type_directory_out(); + + Argument("fixels", "the output fixel directory.").type_directory_out(); OPTIONS - + Option ("dataname", "the name of the output fixel data file encoding peak amplitudes") - + Argument ("path").type_text(); - + +Option("dataname", "the name of the output fixel data file encoding peak amplitudes") + Argument("path").type_text(); } - - -vector get (Image& data) -{ +vector get(Image &data) { data.index(3) = 0; vector result; while (data.index(3) < data.size(3)) { @@ -60,71 +53,71 @@ vector get (Image& data) data.index(3)++; } if (direction.allFinite() && direction.squaredNorm()) - result.push_back (direction); + result.push_back(direction); } return result; } +void run() { + std::string dataname = get_option_value("dataname", ""); - -void run () -{ - std::string dataname = get_option_value ("dataname", ""); - - auto input_header = Header::open (argument[0]); - Peaks::check (input_header); + auto input_header = Header::open(argument[0]); + Peaks::check(input_header); auto input_directions = input_header.get_image(); uint32_t nfixels = 0; bool all_unit_norm = true; - for (auto l = Loop("counting fixels in input image", 0, 3) (input_directions); l; ++l) { - auto dirs = get (input_directions); + for (auto l = Loop("counting fixels in input image", 0, 3)(input_directions); l; ++l) { + auto dirs = get(input_directions); nfixels += dirs.size(); - for (const auto& d : dirs) { - if (MR::abs (d.squaredNorm() - 1.0) > 1e-4) + for (const auto &d : dirs) { + if (MR::abs(d.squaredNorm() - 1.0) > 1e-4) all_unit_norm = false; } } - INFO ("Number of fixels in input peaks image: " + str(nfixels)); + INFO("Number of fixels in input peaks image: " + str(nfixels)); if (all_unit_norm) { if (dataname.size()) { - WARN ("Input peaks image appears to not include amplitude information; " - "requested data file \"" + dataname + "\" will likely contain only ones"); + WARN("Input peaks image appears to not include amplitude information; " + "requested data file \"" + + dataname + "\" will likely contain only ones"); } else { - INFO ("All peaks have unit norm; no need to create amplitudes fixel data file"); + INFO("All peaks have unit norm; no need to create amplitudes fixel data file"); } } else if (!dataname.size()) { dataname = "amplitudes.mif"; - INFO ("Peaks have variable amplitudes; will create additional fixel data file \"" + dataname + "\""); + INFO("Peaks have variable amplitudes; will create additional fixel data file \"" + dataname + "\""); } - Fixel::check_fixel_directory (argument[1], true, true); + Fixel::check_fixel_directory(argument[1], true, true); // Easiest if we first make the index image - const std::string index_path = Path::join (argument[1], "index.mif"); - Header index_header (input_header); + const std::string index_path = Path::join(argument[1], "index.mif"); + Header index_header(input_header); index_header.name() = index_path; index_header.datatype() = DataType::UInt32; index_header.datatype().set_byte_order_native(); index_header.size(3) = 2; index_header.keyval()[Fixel::n_fixels_key] = str(nfixels); - auto index_image = Image::create (index_path, index_header); + auto index_image = Image::create(index_path, index_header); - Header directions_header = Fixel::directions_header_from_index (index_header); + Header directions_header = Fixel::directions_header_from_index(index_header); directions_header.datatype() = DataType::Float32; directions_header.datatype().set_byte_order_native(); - auto directions_image = Image::create (Path::join (argument[1], "directions.mif"), directions_header); + auto directions_image = Image::create(Path::join(argument[1], "directions.mif"), directions_header); Image amplitudes_image; if (dataname.size()) { - Header amplitudes_header = Fixel::data_header_from_index (index_header); - amplitudes_image = Image::create (Path::join (argument[1], dataname), amplitudes_header); + Header amplitudes_header = Fixel::data_header_from_index(index_header); + amplitudes_image = Image::create(Path::join(argument[1], dataname), amplitudes_header); } uint32_t output_index = 0; - for (auto l = Loop("converting peaks to fixel format", 0, 3) (input_directions, index_image); l; ++l) { - auto dirs = get (input_directions); - index_image.index(3) = 0; index_image.value() = dirs.size(); - index_image.index(3) = 1; index_image.value() = dirs.size() ? output_index : 0; + for (auto l = Loop("converting peaks to fixel format", 0, 3)(input_directions, index_image); l; ++l) { + auto dirs = get(input_directions); + index_image.index(3) = 0; + index_image.value() = dirs.size(); + index_image.index(3) = 1; + index_image.value() = dirs.size() ? output_index : 0; for (auto d : dirs) { directions_image.index(0) = output_index; if (amplitudes_image.valid()) { @@ -137,5 +130,4 @@ void run () ++output_index; } } - } diff --git a/cmd/sh2amp.cpp b/cmd/sh2amp.cpp index b273dc2e46..f5bc927677 100644 --- a/cmd/sh2amp.cpp +++ b/cmd/sh2amp.cpp @@ -17,33 +17,30 @@ #include #include "command.h" -#include "image.h" #include "dwi/gradient.h" #include "dwi/shells.h" #include "file/matrix.h" -#include "math/sphere.h" +#include "image.h" #include "math/SH.h" +#include "math/sphere.h" #include "dwi/directions/file.h" - using namespace MR; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Evaluate the amplitude of an image of spherical harmonic functions along specified directions"; DESCRIPTION - + "The input image should consist of a 4D or 5D image, with SH coefficients " - "along the 4th dimension according to the convention below. If 4D (or " - "size 1 along the 5th dimension), the program expects to be provided with " - "a single shell of directions. If 5D, each set of coefficients along the " - "5th dimension is understood to correspond to a different shell." - + "The directions can be provided as:\n" + +"The input image should consist of a 4D or 5D image, with SH coefficients " + "along the 4th dimension according to the convention below. If 4D (or " + "size 1 along the 5th dimension), the program expects to be provided with " + "a single shell of directions. If 5D, each set of coefficients along the " + "5th dimension is understood to correspond to a different shell." + + "The directions can be provided as:\n" "- a 2-column ASCII text file contained azimuth / elevation pairs (as " "produced by dirgen)\n" "- a 3-column ASCII text file containing x, y, z Cartesian direction " @@ -51,141 +48,119 @@ void usage () "- a 4-column ASCII text file containing the x, y, z, b components of a " "full DW encoding scheme (in MRtrix format, see main documentation for " "details).\n" - "- an image file whose header contains a valid DW encoding scheme" - + "If a full DW encoding is provided, the number of shells needs to match " + "- an image file whose header contains a valid DW encoding scheme" + + "If a full DW encoding is provided, the number of shells needs to match " "those found in the input image of coefficients (i.e. its size along the 5th " "dimension). If needed, the -shell option can be used to pick out the " - "specific shell(s) of interest." - + "If the input image contains multiple shells (its size along the 5th " + "specific shell(s) of interest." + + "If the input image contains multiple shells (its size along the 5th " "dimension is greater than one), the program will expect the direction " "set to contain multiple shells, which can only be provided as a full DW " - "encodings (the last two options in the list above)." - + Math::SH::encoding_description; + "encodings (the last two options in the list above)." + + Math::SH::encoding_description; ARGUMENTS - + Argument ("input", - "the input image consisting of spherical harmonic (SH) " - "coefficients.").type_image_in () - + Argument ("directions", - "the list of directions along which the SH functions will " - "be sampled, generated using the dirgen command").type_file_in () - + Argument ("output", - "the output image consisting of the amplitude of the SH " - "functions along the specified directions.").type_image_out (); + +Argument("input", + "the input image consisting of spherical harmonic (SH) " + "coefficients.") + .type_image_in() + + Argument("directions", + "the list of directions along which the SH functions will " + "be sampled, generated using the dirgen command") + .type_file_in() + + Argument("output", + "the output image consisting of the amplitude of the SH " + "functions along the specified directions.") + .type_image_out(); OPTIONS - + Option ("nonnegative", - "cap all negative amplitudes to zero") - + DWI::GradImportOptions() - + Stride::Options - + DataType::options(); + +Option("nonnegative", "cap all negative amplitudes to zero") + DWI::GradImportOptions() + Stride::Options + + DataType::options(); } - using value_type = float; +class SH2Amp { +public: + SH2Amp(const Eigen::MatrixXd &transform, bool nonneg) : transform(transform), nonnegative(nonneg) {} + void operator()(Image &in, Image &out) { + sh = in.row(3); + amp = transform * sh; + if (nonnegative) + amp = amp.cwiseMax(0.0); + out.row(3) = amp; + } - - -class SH2Amp { - public: - SH2Amp (const Eigen::MatrixXd& transform, bool nonneg) : - transform (transform), - nonnegative (nonneg) { } - - void operator() (Image& in, Image& out) { - sh = in.row (3); - amp = transform * sh; - if (nonnegative) - amp = amp.cwiseMax(0.0); - out.row (3) = amp; - } - - private: - const Eigen::MatrixXd& transform; - const bool nonnegative; - Eigen::VectorXd sh, amp; +private: + const Eigen::MatrixXd &transform; + const bool nonnegative; + Eigen::VectorXd sh, amp; }; +class SH2AmpMultiShell { +public: + SH2AmpMultiShell(const vector &dirs, const DWI::Shells &shells, bool nonneg) + : transforms(dirs), shells(shells), nonnegative(nonneg) {} + void operator()(Image &in, Image &out) { + for (size_t n = 0; n < transforms.size(); ++n) { + if (in.ndim() > 4) + in.index(4) = n; + sh = in.row(3); + amp = transforms[n] * sh; + if (nonnegative) + amp = amp.cwiseMax(value_type(0.0)); - -class SH2AmpMultiShell { - public: - SH2AmpMultiShell (const vector& dirs, const DWI::Shells& shells, bool nonneg) : - transforms (dirs), - shells (shells), - nonnegative (nonneg) { } - - void operator() (Image& in, Image& out) { - for (size_t n = 0; n < transforms.size(); ++n) { - if (in.ndim() > 4) - in.index(4) = n; - sh = in.row (3); - - amp = transforms[n] * sh; - - if (nonnegative) - amp = amp.cwiseMax(value_type(0.0)); - - for (ssize_t k = 0; k < amp.size(); ++k) { - out.index(3) = shells[n].get_volumes()[k]; - out.value() = amp[k]; - } + for (ssize_t k = 0; k < amp.size(); ++k) { + out.index(3) = shells[n].get_volumes()[k]; + out.value() = amp[k]; } } + } - private: - const vector& transforms; - const DWI::Shells& shells; - const bool nonnegative; - Eigen::VectorXd sh, amp; +private: + const vector &transforms; + const DWI::Shells &shells; + const bool nonnegative; + Eigen::VectorXd sh, amp; }; - - - - - -void run () -{ +void run() { auto sh_data = Image::open(argument[0]); - Math::SH::check (sh_data); - const size_t lmax = Math::SH::LforN (sh_data.size(3)); + Math::SH::check(sh_data); + const size_t lmax = Math::SH::LforN(sh_data.size(3)); Eigen::MatrixXd directions; try { - directions = DWI::Directions::load_spherical (argument[1]); - } - catch (Exception& E) { + directions = DWI::Directions::load_spherical(argument[1]); + } catch (Exception &E) { try { - directions = File::Matrix::load_matrix (argument[1]); + directions = File::Matrix::load_matrix(argument[1]); if (directions.cols() < 4) - throw ("unable to interpret file \"" + std::string(argument[1]) + "\" as a directions or gradient file"); - } - catch (Exception& E) { - auto header = Header::open (argument[1]); - directions = DWI::get_DW_scheme (header); + throw("unable to interpret file \"" + std::string(argument[1]) + "\" as a directions or gradient file"); + } catch (Exception &E) { + auto header = Header::open(argument[1]); + directions = DWI::get_DW_scheme(header); } } if (!directions.size()) - throw Exception ("no directions found in input directions file"); + throw Exception("no directions found in input directions file"); - Header amp_header (sh_data); + Header amp_header(sh_data); amp_header.ndim() = 4; amp_header.size(3) = directions.rows(); - Stride::set_from_command_line (amp_header, Stride::contiguous_along_axis (3, amp_header)); - amp_header.datatype() = DataType::from_command_line (DataType::Float32); + Stride::set_from_command_line(amp_header, Stride::contiguous_along_axis(3, amp_header)); + amp_header.datatype() = DataType::from_command_line(DataType::Float32); if (directions.cols() == 2) { // single-shell: if (sh_data.ndim() > 4 && sh_data.size(4) > 1) { - Exception excp ("multi-shell input data provided with single-shell direction set"); - excp.push_back (" use full DW scheme to operate on multi-shell data"); + Exception excp("multi-shell input data provided with single-shell direction set"); + excp.push_back(" use full DW scheme to operate on multi-shell data"); throw excp; } @@ -193,53 +168,48 @@ void run () std::stringstream dir_stream; for (ssize_t d = 0; d < directions.rows() - 1; ++d) - dir_stream << directions(d,0) << "," << directions(d,1) << "\n"; - dir_stream << directions(directions.rows() - 1,0) << "," << directions(directions.rows() - 1,1); + dir_stream << directions(d, 0) << "," << directions(d, 1) << "\n"; + dir_stream << directions(directions.rows() - 1, 0) << "," << directions(directions.rows() - 1, 1); amp_header.keyval()["directions"] = dir_stream.str(); auto amp_data = Image::create(argument[2], amp_header); - auto transform = Math::SH::init_transform (directions, lmax); + auto transform = Math::SH::init_transform(directions, lmax); - SH2Amp sh2amp (transform, get_options("nonnegative").size()); - ThreadedLoop("computing amplitudes", sh_data, 0, 3, 2).run (sh2amp, sh_data, amp_data); + SH2Amp sh2amp(transform, get_options("nonnegative").size()); + ThreadedLoop("computing amplitudes", sh_data, 0, 3, 2).run(sh2amp, sh_data, amp_data); - } - else { // full gradient scheme: + } else { // full gradient scheme: - DWI::set_DW_scheme (amp_header, directions); - auto shells = DWI::Shells (directions).select_shells (false, false, false); + DWI::set_DW_scheme(amp_header, directions); + auto shells = DWI::Shells(directions).select_shells(false, false, false); if (shells.count() == 0) - throw Exception ("no shells found in gradient scheme"); + throw Exception("no shells found in gradient scheme"); if (shells.count() > 1) { if (sh_data.ndim() < 5) - throw Exception ("multiple shells detected in gradient scheme, but only one shell in input data"); - if (sh_data.size(4) != ssize_t (shells.count())) - throw Exception ("number of shells differs between gradient scheme and input data"); - } - else if (! (sh_data.ndim() == 4 || ( sh_data.ndim() > 4 && ( sh_data.size(4) != 1 ))) ) - throw Exception ("number of shells differs between gradient scheme and input data"); + throw Exception("multiple shells detected in gradient scheme, but only one shell in input data"); + if (sh_data.size(4) != ssize_t(shells.count())) + throw Exception("number of shells differs between gradient scheme and input data"); + } else if (!(sh_data.ndim() == 4 || (sh_data.ndim() > 4 && (sh_data.size(4) != 1)))) + throw Exception("number of shells differs between gradient scheme and input data"); vector transforms; for (size_t n = 0; n < shells.count(); ++n) { - Eigen::MatrixXd dirs (shells[n].count(), 2); + Eigen::MatrixXd dirs(shells[n].count(), 2); if (shells[n].is_bzero()) { - dirs.setConstant (0.0); - } - else { + dirs.setConstant(0.0); + } else { for (size_t idx = 0; idx < shells[n].count(); ++idx) - Math::Sphere::cartesian2spherical (directions.row (shells[n].get_volumes()[idx]).head (3), dirs.row (idx)); + Math::Sphere::cartesian2spherical(directions.row(shells[n].get_volumes()[idx]).head(3), dirs.row(idx)); } - transforms.push_back (Math::SH::init_transform (dirs, lmax)); + transforms.push_back(Math::SH::init_transform(dirs, lmax)); } auto amp_data = Image::create(argument[2], amp_header); - SH2AmpMultiShell sh2amp (transforms, shells, get_options("nonnegative").size()); - ThreadedLoop("computing amplitudes", sh_data, 0, 3).run (sh2amp, sh_data, amp_data); - + SH2AmpMultiShell sh2amp(transforms, shells, get_options("nonnegative").size()); + ThreadedLoop("computing amplitudes", sh_data, 0, 3).run(sh2amp, sh_data, amp_data); } } - diff --git a/cmd/sh2peaks.cpp b/cmd/sh2peaks.cpp index e721ee85e6..ed28267904 100644 --- a/cmd/sh2peaks.cpp +++ b/cmd/sh2peaks.cpp @@ -14,15 +14,14 @@ * For more details, see http://www.mrtrix.org/. */ +#include "algo/loop.h" #include "command.h" +#include "file/matrix.h" +#include "image.h" +#include "math/SH.h" #include "memory.h" #include "progressbar.h" #include "thread_queue.h" -#include "image.h" -#include "algo/loop.h" -#include "file/matrix.h" -#include "math/SH.h" - #define DOT_THRESHOLD 0.99 #define DEFAULT_NPEAKS 3 @@ -30,306 +29,280 @@ using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Extract the peaks of a spherical harmonic function in each voxel"; DESCRIPTION - + "Peaks of the spherical harmonic function in each voxel are located by " - "commencing a Newton search along each of a set of pre-specified directions"; + +"Peaks of the spherical harmonic function in each voxel are located by " + "commencing a Newton search along each of a set of pre-specified directions"; DESCRIPTION - + Math::SH::encoding_description; + +Math::SH::encoding_description; ARGUMENTS - + Argument ("SH", "the input image of SH coefficients.") - .type_image_in () + +Argument("SH", "the input image of SH coefficients.").type_image_in() - + Argument ("output", - "the output image. Each volume corresponds to the x, y & z component " - "of each peak direction vector in turn.") - .type_image_out (); + + Argument("output", + "the output image. Each volume corresponds to the x, y & z component " + "of each peak direction vector in turn.") + .type_image_out(); OPTIONS - + Option ("num", "the number of peaks to extract (default: " + str(DEFAULT_NPEAKS) + ").") - + Argument ("peaks").type_integer (0) + +Option("num", "the number of peaks to extract (default: " + str(DEFAULT_NPEAKS) + ").") + + Argument("peaks").type_integer(0) - + Option ("direction", - "the direction of a peak to estimate. The algorithm will attempt to " - "find the same number of peaks as have been specified using this option.") - .allow_multiple() - + Argument ("phi").type_float() - + Argument ("theta").type_float() + + Option("direction", + "the direction of a peak to estimate. The algorithm will attempt to " + "find the same number of peaks as have been specified using this option.") + .allow_multiple() + + Argument("phi").type_float() + Argument("theta").type_float() - + Option ("peaks", - "the program will try to find the peaks that most closely match those " - "in the image provided.") - + Argument ("image").type_image_in() + + Option("peaks", + "the program will try to find the peaks that most closely match those " + "in the image provided.") + + Argument("image").type_image_in() - + Option ("threshold", - "only peak amplitudes greater than the threshold will be considered.") - + Argument ("value").type_float (0.0) + + Option("threshold", "only peak amplitudes greater than the threshold will be considered.") + + Argument("value").type_float(0.0) - + Option ("seeds", - "specify a set of directions from which to start the multiple restarts of " - "the optimisation (by default, the built-in 60 direction set is used)") - + Argument ("file").type_file_in() + + Option("seeds", + "specify a set of directions from which to start the multiple restarts of " + "the optimisation (by default, the built-in 60 direction set is used)") + + Argument("file").type_file_in() - + Option ("mask", - "only perform computation within the specified binary brain mask image.") - + Argument ("image").type_image_in() + + Option("mask", "only perform computation within the specified binary brain mask image.") + + Argument("image").type_image_in() - + Option ("fast", - "use lookup table to compute associated Legendre polynomials (faster, but approximate)."); + + Option("fast", "use lookup table to compute associated Legendre polynomials (faster, but approximate)."); REFERENCES - + "Jeurissen, B.; Leemans, A.; Tournier, J.-D.; Jones, D.K.; Sijbers, J. " - "Investigating the prevalence of complex fiber configurations in white matter tissue with diffusion magnetic resonance imaging. " - "Human Brain Mapping, 2013, 34(11), 2747-2766"; + +"Jeurissen, B.; Leemans, A.; Tournier, J.-D.; Jones, D.K.; Sijbers, J. " + "Investigating the prevalence of complex fiber configurations in white matter tissue with diffusion magnetic " + "resonance imaging. " + "Human Brain Mapping, 2013, 34(11), 2747-2766"; } - using value_type = float; - - -class Direction { - public: - Direction () : a (NaN) { } - Direction (const Direction& d) : a (d.a), v (d.v) { } - Direction (value_type phi, value_type theta) : a (1.0), v (std::cos (phi) *std::sin (theta), std::sin (phi) *std::sin (theta), std::cos (theta)) { } - value_type a; - Eigen::Vector3f v; - bool operator< (const Direction& d) const { - return (a > d.a); - } +class Direction { +public: + Direction() : a(NaN) {} + Direction(const Direction &d) : a(d.a), v(d.v) {} + Direction(value_type phi, value_type theta) + : a(1.0), v(std::cos(phi) * std::sin(theta), std::sin(phi) * std::sin(theta), std::cos(theta)) {} + value_type a; + Eigen::Vector3f v; + bool operator<(const Direction &d) const { return (a > d.a); } }; - - - -class Item { - public: - Eigen::VectorXf data; - ssize_t pos[3]; +class Item { +public: + Eigen::VectorXf data; + ssize_t pos[3]; }; +class DataLoader { +public: + DataLoader(Image &sh_data, const Image &mask_data) + : sh(sh_data), mask(mask_data), loop(Loop("estimating peak directions", 0, 3)(sh)) {} + + bool operator()(Item &item) { + if (loop) { + item.data.resize(sh.size(3)); + item.pos[0] = sh.index(0); + item.pos[1] = sh.index(1); + item.pos[2] = sh.index(2); + + if (mask.valid()) + assign_pos_of(sh, 0, 3).to(mask); + if (mask.valid() && !mask.value()) { + for (auto l = Loop(3)(sh); l; ++l) + item.data[sh.index(3)] = NaN; + } else { + // iterates over SH coefficients + for (auto l = Loop(3)(sh); l; ++l) + item.data[sh.index(3)] = sh.value(); + } + loop++; - - -class DataLoader { - public: - DataLoader (Image& sh_data, - const Image& mask_data) : - sh (sh_data), - mask (mask_data), - loop (Loop("estimating peak directions", 0, 3) (sh)) { } - - bool operator() (Item& item) { - if (loop) { - item.data.resize (sh.size(3)); - item.pos[0] = sh.index(0); - item.pos[1] = sh.index(1); - item.pos[2] = sh.index(2); - - if (mask.valid()) - assign_pos_of(sh, 0, 3).to(mask); - if (mask.valid() && !mask.value()) { - for (auto l = Loop(3) (sh); l; ++l) - item.data[sh.index(3)] = NaN; - } else { - // iterates over SH coefficients - for (auto l = Loop(3) (sh); l; ++l) - item.data[sh.index(3)] = sh.value(); - } - - loop++; - - return true; - } - return false; + return true; } + return false; + } - private: - Image sh; - Image mask; - LoopAlongAxisRangeProgress::Run > loop; +private: + Image sh; + Image mask; + LoopAlongAxisRangeProgress::Run> loop; }; +class Processor { +public: + Processor(Image &dirs_data, + Eigen::Matrix &directions, + int lmax, + int npeaks, + vector true_peaks, + value_type threshold, + Image ipeaks_data, + bool use_precomputer) + : dirs_vox(dirs_data), + dirs(directions), + lmax(lmax), + npeaks(npeaks), + true_peaks(true_peaks), + threshold(threshold), + peaks_out(npeaks), + ipeaks_vox(ipeaks_data), + precomputer(use_precomputer ? new Math::SH::PrecomputedAL(lmax) : nullptr) {} + + bool operator()(const Item &item) { + + dirs_vox.index(0) = item.pos[0]; + dirs_vox.index(1) = item.pos[1]; + dirs_vox.index(2) = item.pos[2]; + + if (check_input(item)) { + for (auto l = Loop(3)(dirs_vox); l; ++l) + dirs_vox.value() = NaN; + return true; + } + vector all_peaks; - -class Processor { - public: - Processor (Image& dirs_data, - Eigen::Matrix& directions, - int lmax, - int npeaks, - vector true_peaks, - value_type threshold, - Image ipeaks_data, - bool use_precomputer) : - dirs_vox (dirs_data), - dirs (directions), - lmax (lmax), - npeaks (npeaks), - true_peaks (true_peaks), - threshold (threshold), - peaks_out (npeaks), - ipeaks_vox (ipeaks_data), - precomputer (use_precomputer ? new Math::SH::PrecomputedAL (lmax) : nullptr) { } - - bool operator() (const Item& item) { - - dirs_vox.index(0) = item.pos[0]; - dirs_vox.index(1) = item.pos[1]; - dirs_vox.index(2) = item.pos[2]; - - if (check_input (item)) { - for (auto l = Loop(3) (dirs_vox); l; ++l) - dirs_vox.value() = NaN; - return true; - } - - vector all_peaks; - - for (size_t i = 0; i < size_t(dirs.rows()); i++) { - Direction p (dirs (i,0), dirs (i,1)); - p.a = Math::SH::get_peak (item.data, lmax, p.v, precomputer); - if (std::isfinite (p.a)) { - for (size_t j = 0; j < all_peaks.size(); j++) { - if (abs (p.v.dot (all_peaks[j].v)) > DOT_THRESHOLD) { - p.a = NAN; - break; - } + for (size_t i = 0; i < size_t(dirs.rows()); i++) { + Direction p(dirs(i, 0), dirs(i, 1)); + p.a = Math::SH::get_peak(item.data, lmax, p.v, precomputer); + if (std::isfinite(p.a)) { + for (size_t j = 0; j < all_peaks.size(); j++) { + if (abs(p.v.dot(all_peaks[j].v)) > DOT_THRESHOLD) { + p.a = NAN; + break; } } - if (std::isfinite (p.a) && p.a >= threshold) - all_peaks.push_back (p); } + if (std::isfinite(p.a) && p.a >= threshold) + all_peaks.push_back(p); + } - if (ipeaks_vox.valid()) { - ipeaks_vox.index(0) = item.pos[0]; - ipeaks_vox.index(1) = item.pos[1]; - ipeaks_vox.index(2) = item.pos[2]; - - for (int i = 0; i < npeaks; i++) { - Eigen::Vector3f p; - ipeaks_vox.index(3) = 3*i; - for (int n = 0; n < 3; n++) { - p[n] = ipeaks_vox.value(); - ipeaks_vox.index(3)++; - } - p.normalize(); - - value_type mdot = 0.0; - for (size_t n = 0; n < all_peaks.size(); n++) { - value_type f = abs (p.dot (all_peaks[n].v)); - if (f > mdot) { - mdot = f; - peaks_out[i] = all_peaks[n]; - } + if (ipeaks_vox.valid()) { + ipeaks_vox.index(0) = item.pos[0]; + ipeaks_vox.index(1) = item.pos[1]; + ipeaks_vox.index(2) = item.pos[2]; + + for (int i = 0; i < npeaks; i++) { + Eigen::Vector3f p; + ipeaks_vox.index(3) = 3 * i; + for (int n = 0; n < 3; n++) { + p[n] = ipeaks_vox.value(); + ipeaks_vox.index(3)++; + } + p.normalize(); + + value_type mdot = 0.0; + for (size_t n = 0; n < all_peaks.size(); n++) { + value_type f = abs(p.dot(all_peaks[n].v)); + if (f > mdot) { + mdot = f; + peaks_out[i] = all_peaks[n]; } } } - else if (true_peaks.size()) { - for (int i = 0; i < npeaks; i++) { - value_type mdot = 0.0; - for (size_t n = 0; n < all_peaks.size(); n++) { - value_type f = abs (all_peaks[n].v.dot (true_peaks[i].v)); - if (f > mdot) { - mdot = f; - peaks_out[i] = all_peaks[n]; - } + } else if (true_peaks.size()) { + for (int i = 0; i < npeaks; i++) { + value_type mdot = 0.0; + for (size_t n = 0; n < all_peaks.size(); n++) { + value_type f = abs(all_peaks[n].v.dot(true_peaks[i].v)); + if (f > mdot) { + mdot = f; + peaks_out[i] = all_peaks[n]; } } } - else std::partial_sort_copy (all_peaks.begin(), all_peaks.end(), peaks_out.begin(), peaks_out.end()); - - int actual_npeaks = std::min (npeaks, (int) all_peaks.size()); - dirs_vox.index(3) = 0; - for (int n = 0; n < actual_npeaks; n++) { - dirs_vox.value() = peaks_out[n].a*peaks_out[n].v[0]; - dirs_vox.index(3)++; - dirs_vox.value() = peaks_out[n].a*peaks_out[n].v[1]; - dirs_vox.index(3)++; - dirs_vox.value() = peaks_out[n].a*peaks_out[n].v[2]; - dirs_vox.index(3)++; - } - for (; dirs_vox.index(3) < 3*npeaks; dirs_vox.index(3)++) dirs_vox.value() = NaN; - - return true; + } else + std::partial_sort_copy(all_peaks.begin(), all_peaks.end(), peaks_out.begin(), peaks_out.end()); + + int actual_npeaks = std::min(npeaks, (int)all_peaks.size()); + dirs_vox.index(3) = 0; + for (int n = 0; n < actual_npeaks; n++) { + dirs_vox.value() = peaks_out[n].a * peaks_out[n].v[0]; + dirs_vox.index(3)++; + dirs_vox.value() = peaks_out[n].a * peaks_out[n].v[1]; + dirs_vox.index(3)++; + dirs_vox.value() = peaks_out[n].a * peaks_out[n].v[2]; + dirs_vox.index(3)++; } + for (; dirs_vox.index(3) < 3 * npeaks; dirs_vox.index(3)++) + dirs_vox.value() = NaN; - private: - Image dirs_vox; - Eigen::Matrix dirs; - int lmax, npeaks; - vector true_peaks; - value_type threshold; - vector peaks_out; - Image ipeaks_vox; - Math::SH::PrecomputedAL* precomputer; - - bool check_input (const Item& item) { - if (ipeaks_vox.valid()) { - ipeaks_vox.index(0) = item.pos[0]; - ipeaks_vox.index(1) = item.pos[1]; - ipeaks_vox.index(2) = item.pos[2]; - ipeaks_vox.index(3) = 0; - if (std::isnan (value_type (ipeaks_vox.value()))) - return true; - } - - bool no_peaks = true; - for (size_t i = 0; i < size_t(item.data.size()); i++) { - if (std::isnan (item.data[i])) - return true; - if (no_peaks) - if (i && item.data[i] != 0.0) - no_peaks = false; - } + return true; + } - return no_peaks; +private: + Image dirs_vox; + Eigen::Matrix dirs; + int lmax, npeaks; + vector true_peaks; + value_type threshold; + vector peaks_out; + Image ipeaks_vox; + Math::SH::PrecomputedAL *precomputer; + + bool check_input(const Item &item) { + if (ipeaks_vox.valid()) { + ipeaks_vox.index(0) = item.pos[0]; + ipeaks_vox.index(1) = item.pos[1]; + ipeaks_vox.index(2) = item.pos[2]; + ipeaks_vox.index(3) = 0; + if (std::isnan(value_type(ipeaks_vox.value()))) + return true; } -}; + bool no_peaks = true; + for (size_t i = 0; i < size_t(item.data.size()); i++) { + if (std::isnan(item.data[i])) + return true; + if (no_peaks) + if (i && item.data[i] != 0.0) + no_peaks = false; + } -extern value_type default_directions []; - + return no_peaks; + } +}; +extern value_type default_directions[]; -void run () -{ - auto SH_data = Image::open (argument[0]).with_direct_io (3); - Math::SH::check (SH_data); +void run() { + auto SH_data = Image::open(argument[0]).with_direct_io(3); + Math::SH::check(SH_data); - auto opt = get_options ("mask"); + auto opt = get_options("mask"); Image mask_data; if (opt.size()) - mask_data = Image::open (opt[0][0]); + mask_data = Image::open(opt[0][0]); - opt = get_options ("seeds"); + opt = get_options("seeds"); Eigen::Matrix dirs; if (opt.size()) - dirs = File::Matrix::load_matrix (opt[0][0]); + dirs = File::Matrix::load_matrix(opt[0][0]); else { - dirs = Eigen::Map > (default_directions, 60, 2); + dirs = Eigen::Map>(default_directions, 60, 2); } if (dirs.cols() != 2) - throw Exception ("expecting 2 columns for search directions matrix"); + throw Exception("expecting 2 columns for search directions matrix"); - int npeaks = get_option_value ("num", DEFAULT_NPEAKS); + int npeaks = get_option_value("num", DEFAULT_NPEAKS); - opt = get_options ("direction"); + opt = get_options("direction"); vector true_peaks; for (size_t n = 0; n < opt.size(); ++n) { - Direction p (Math::pi*to (opt[n][0]) /180.0, Math::pi*float (opt[n][1]) /180.0); - true_peaks.push_back (p); + Direction p(Math::pi * to(opt[n][0]) / 180.0, Math::pi * float(opt[n][1]) / 180.0); + true_peaks.push_back(p); } if (true_peaks.size()) npeaks = true_peaks.size(); @@ -339,88 +312,43 @@ void run () auto header = Header(SH_data); header.datatype() = DataType::Float32; - opt = get_options ("peaks"); + opt = get_options("peaks"); Image ipeaks_data; if (opt.size()) { if (true_peaks.size()) - throw Exception ("you can't specify both a peaks file and orientations to be estimated at the same time"); + throw Exception("you can't specify both a peaks file and orientations to be estimated at the same time"); if (opt.size()) ipeaks_data = Image::open(opt[0][0]); - check_dimensions (SH_data, ipeaks_data, 0, 3); - npeaks = ipeaks_data.size (3) / 3; + check_dimensions(SH_data, ipeaks_data, 0, 3); + npeaks = ipeaks_data.size(3) / 3; } header.size(3) = 3 * npeaks; - auto peaks = Image::create (argument[1], header); - - DataLoader loader (SH_data, mask_data); - Processor processor (peaks, dirs, Math::SH::LforN (SH_data.size (3)), - npeaks, true_peaks, threshold, ipeaks_data, get_options("fast").size()); - - Thread::run_queue (loader, Thread::batch (Item()), Thread::multi (processor)); + auto peaks = Image::create(argument[1], header); + + DataLoader loader(SH_data, mask_data); + Processor processor(peaks, + dirs, + Math::SH::LforN(SH_data.size(3)), + npeaks, + true_peaks, + threshold, + ipeaks_data, + get_options("fast").size()); + + Thread::run_queue(loader, Thread::batch(Item()), Thread::multi(processor)); } - -value_type default_directions [] = { - 0, 0, - -3.14159, 1.3254, - -2.58185, 1.50789, - 2.23616, 1.46585, - 0.035637, 0.411961, - 2.65836, 0.913741, - 0.780743, 1.23955, - -0.240253, 1.58088, - -0.955334, 1.08447, - 1.12534, 1.78765, - 1.12689, 1.30126, - 0.88512, 1.55615, - 2.08019, 1.16222, - 0.191423, 1.06076, - 1.29453, 0.707568, - 2.794, 1.24245, - 2.02138, 0.337172, - 1.59186, 1.30164, - -2.83601, 0.910221, - 0.569095, 0.96362, - 3.05336, 1.00206, - 2.4406, 1.19129, - 0.437969, 1.30795, - 0.247623, 0.728643, - -0.193887, 1.0467, - -1.34638, 1.14233, - 1.35977, 1.54693, - 1.82433, 0.660035, - -0.766769, 1.3685, - -2.02757, 1.02063, - -0.78071, 0.667313, - -1.47543, 1.45516, - -1.10765, 1.38916, - -1.65789, 0.871848, - 1.89902, 1.44647, - 3.08122, 0.336433, - -2.35317, 1.25244, - 2.54757, 0.586206, - -2.14697, 0.338323, - 3.10764, 0.670594, - 1.75238, 0.991972, - -1.21593, 0.82585, - -0.259942, 0.71572, - -1.51829, 0.549286, - 2.22968, 0.851973, - 0.979108, 0.954864, - 1.36274, 1.04186, - -0.0104792, 1.33716, - -0.891568, 0.33526, - -2.0635, 0.68273, - -2.41353, 0.917031, - 2.57199, 1.50166, - 0.965936, 0.33624, - 0.763244, 0.657346, - -2.61583, 0.606725, - -0.429332, 1.30226, - -2.91118, 1.56901, - -2.79822, 1.24559, - -1.70453, 1.20406, - -0.582782, 0.975235 -}; - +value_type default_directions[] = { + 0, 0, -3.14159, 1.3254, -2.58185, 1.50789, 2.23616, 1.46585, 0.035637, 0.411961, + 2.65836, 0.913741, 0.780743, 1.23955, -0.240253, 1.58088, -0.955334, 1.08447, 1.12534, 1.78765, + 1.12689, 1.30126, 0.88512, 1.55615, 2.08019, 1.16222, 0.191423, 1.06076, 1.29453, 0.707568, + 2.794, 1.24245, 2.02138, 0.337172, 1.59186, 1.30164, -2.83601, 0.910221, 0.569095, 0.96362, + 3.05336, 1.00206, 2.4406, 1.19129, 0.437969, 1.30795, 0.247623, 0.728643, -0.193887, 1.0467, + -1.34638, 1.14233, 1.35977, 1.54693, 1.82433, 0.660035, -0.766769, 1.3685, -2.02757, 1.02063, + -0.78071, 0.667313, -1.47543, 1.45516, -1.10765, 1.38916, -1.65789, 0.871848, 1.89902, 1.44647, + 3.08122, 0.336433, -2.35317, 1.25244, 2.54757, 0.586206, -2.14697, 0.338323, 3.10764, 0.670594, + 1.75238, 0.991972, -1.21593, 0.82585, -0.259942, 0.71572, -1.51829, 0.549286, 2.22968, 0.851973, + 0.979108, 0.954864, 1.36274, 1.04186, -0.0104792, 1.33716, -0.891568, 0.33526, -2.0635, 0.68273, + -2.41353, 0.917031, 2.57199, 1.50166, 0.965936, 0.33624, 0.763244, 0.657346, -2.61583, 0.606725, + -0.429332, 1.30226, -2.91118, 1.56901, -2.79822, 1.24559, -1.70453, 1.20406, -0.582782, 0.975235}; diff --git a/cmd/sh2power.cpp b/cmd/sh2power.cpp index 0a73652b69..f07b8c8263 100644 --- a/cmd/sh2power.cpp +++ b/cmd/sh2power.cpp @@ -14,87 +14,84 @@ * For more details, see http://www.mrtrix.org/. */ +#include "algo/threaded_loop.h" #include "command.h" #include "image.h" #include "math/SH.h" -#include "algo/threaded_loop.h" - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Compute the total power of a spherical harmonics image"; DESCRIPTION - + "This command computes the sum of squared SH coefficients, " - "which equals the mean-squared amplitude " - "of the spherical function it represents." + +"This command computes the sum of squared SH coefficients, " + "which equals the mean-squared amplitude " + "of the spherical function it represents." - + Math::SH::encoding_description; + + Math::SH::encoding_description; ARGUMENTS - + Argument ("SH", "the input spherical harmonics coefficients image.").type_image_in () - + Argument ("power", "the output power image.").type_image_out (); + +Argument("SH", "the input spherical harmonics coefficients image.").type_image_in() + + Argument("power", "the output power image.").type_image_out(); OPTIONS - + Option ("spectrum", "output the power spectrum, i.e., the power contained within each harmonic degree (l=0, 2, 4, ...) as a 4-D image."); - + +Option("spectrum", + "output the power spectrum, i.e., the power contained within each harmonic degree (l=0, 2, 4, ...) as a 4-D " + "image."); } - -void run () { +void run() { auto SH_data = Image::open(argument[0]); - Math::SH::check (SH_data); + Math::SH::check(SH_data); - Header power_header (SH_data); + Header power_header(SH_data); bool spectrum = get_options("spectrum").size(); - int lmax = Math::SH::LforN (SH_data.size (3)); - INFO ("calculating spherical harmonic power up to degree " + str (lmax)); + int lmax = Math::SH::LforN(SH_data.size(3)); + INFO("calculating spherical harmonic power up to degree " + str(lmax)); if (spectrum) - power_header.size (3) = 1 + lmax/2; + power_header.size(3) = 1 + lmax / 2; else power_header.ndim() = 3; power_header.datatype() = DataType::Float32; auto power_data = Image::create(argument[1], power_header); - auto f1 = [&] (decltype(power_data)& P, decltype(SH_data)& SH) { + auto f1 = [&](decltype(power_data) &P, decltype(SH_data) &SH) { P.index(3) = 0; - for (int l = 0; l <= lmax; l+=2) { + for (int l = 0; l <= lmax; l += 2) { float power = 0.0; for (int m = -l; m <= l; ++m) { - SH.index(3) = Math::SH::index (l, m); + SH.index(3) = Math::SH::index(l, m); float val = SH.value(); - power += Math::pow2 (val); + power += Math::pow2(val); } P.value() = power / (Math::pi * 4); ++P.index(3); } }; - auto f2 = [&] (decltype(power_data)& P, decltype(SH_data)& SH) { + auto f2 = [&](decltype(power_data) &P, decltype(SH_data) &SH) { float power = 0.0; - for (int l = 0; l <= lmax; l+=2) { + for (int l = 0; l <= lmax; l += 2) { for (int m = -l; m <= l; ++m) { - SH.index(3) = Math::SH::index (l, m); + SH.index(3) = Math::SH::index(l, m); float val = SH.value(); - power += Math::pow2 (val); + power += Math::pow2(val); } } P.value() = power / (Math::pi * 4); }; - auto loop = ThreadedLoop ("calculating SH power", SH_data, 0, 3); + auto loop = ThreadedLoop("calculating SH power", SH_data, 0, 3); if (spectrum) loop.run(f1, power_data, SH_data); else loop.run(f2, power_data, SH_data); - } diff --git a/cmd/sh2response.cpp b/cmd/sh2response.cpp index faf4b9ed51..4bd977759a 100644 --- a/cmd/sh2response.cpp +++ b/cmd/sh2response.cpp @@ -14,89 +14,76 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "exception.h" -#include "image.h" -#include "mrtrix.h" -#include "progressbar.h" #include "algo/loop.h" +#include "command.h" #include "dwi/gradient.h" #include "dwi/shells.h" +#include "exception.h" #include "file/matrix.h" +#include "image.h" #include "math/SH.h" #include "math/ZSH.h" - - - - - +#include "mrtrix.h" +#include "progressbar.h" using namespace MR; using namespace App; - - - - - -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Generate an appropriate response function from the image data for spherical deconvolution"; DESCRIPTION - + Math::SH::encoding_description; + +Math::SH::encoding_description; ARGUMENTS - + Argument ("SH", "the spherical harmonic decomposition of the diffusion-weighted images").type_image_in() - + Argument ("mask", "the mask containing the voxels from which to estimate the response function").type_image_in() - + Argument ("directions", "a 4D image containing the direction vectors along which to estimate the response function").type_image_in() - + Argument ("response", "the output axially-symmetric spherical harmonic coefficients").type_file_out(); + +Argument("SH", "the spherical harmonic decomposition of the diffusion-weighted images").type_image_in() + + Argument("mask", "the mask containing the voxels from which to estimate the response function").type_image_in() + + Argument("directions", + "a 4D image containing the direction vectors along which to estimate the response function") + .type_image_in() + + Argument("response", "the output axially-symmetric spherical harmonic coefficients").type_file_out(); OPTIONS - + Option ("lmax", "specify the maximum harmonic degree of the response function to estimate") - + Argument ("value").type_integer (0, 20) - + Option ("dump", "dump the m=0 SH coefficients from all voxels in the mask to the output file, rather than their mean") - + Argument ("file").type_file_out(); + +Option("lmax", "specify the maximum harmonic degree of the response function to estimate") + + Argument("value").type_integer(0, 20) + + Option("dump", + "dump the m=0 SH coefficients from all voxels in the mask to the output file, rather than their mean") + + Argument("file").type_file_out(); } - - using value_type = double; - - -void run () -{ +void run() { auto SH = Image::open(argument[0]); - Math::SH::check (SH); + Math::SH::check(SH); auto mask = Image::open(argument[1]); auto dir = Image::open(argument[2]).with_direct_io(); - int lmax = get_option_value ("lmax", Math::SH::LforN (SH.size(3))); + int lmax = get_option_value("lmax", Math::SH::LforN(SH.size(3))); - check_dimensions (SH, mask, 0, 3); - check_dimensions (SH, dir, 0, 3); + check_dimensions(SH, mask, 0, 3); + check_dimensions(SH, dir, 0, 3); if (dir.ndim() != 4) - throw Exception ("input direction image \"" + std::string (argument[2]) + "\" must be a 4D image"); + throw Exception("input direction image \"" + std::string(argument[2]) + "\" must be a 4D image"); if (dir.size(3) != 3) - throw Exception ("input direction image \"" + std::string (argument[2]) + "\" must contain precisely 3 volumes"); + throw Exception("input direction image \"" + std::string(argument[2]) + "\" must contain precisely 3 volumes"); Eigen::VectorXd delta; - Eigen::VectorXd response = Eigen::VectorXd::Zero (Math::ZSH::NforL (lmax)); + Eigen::VectorXd response = Eigen::VectorXd::Zero(Math::ZSH::NforL(lmax)); size_t count = 0; File::OFStream dump_stream; - auto opt = get_options ("dump"); + auto opt = get_options("dump"); if (opt.size()) - dump_stream.open (opt[0][0]); + dump_stream.open(opt[0][0]); - Eigen::Matrix AL (lmax+1); - Math::Legendre::Plm_sph (AL, lmax, 0, value_type (1.0)); + Eigen::Matrix AL(lmax + 1); + Math::Legendre::Plm_sph(AL, lmax, 0, value_type(1.0)); - auto loop = Loop ("estimating response function", SH, 0, 3); + auto loop = Loop("estimating response function", SH, 0, 3); for (auto l = loop(mask, SH, dir); l; ++l) { if (!mask.value()) @@ -104,28 +91,30 @@ void run () Eigen::Vector3d d = dir.row(3); if (!d.allFinite()) { - WARN ("voxel with invalid direction [ " + str(dir.index(0)) + " " + str(dir.index(1)) + " " + str(dir.index(2)) + " ]; skipping"); + WARN("voxel with invalid direction [ " + str(dir.index(0)) + " " + str(dir.index(1)) + " " + str(dir.index(2)) + + " ]; skipping"); continue; } d.normalize(); // Uncertainty regarding Eigen's behaviour when normalizing a zero vector; may change behaviour between versions if (!d.allFinite() || !d.squaredNorm()) { - WARN ("voxel with zero direction [ " + str(dir.index(0)) + " " + str(dir.index(1)) + " " + str(dir.index(2)) + " ]; skipping"); + WARN("voxel with zero direction [ " + str(dir.index(0)) + " " + str(dir.index(1)) + " " + str(dir.index(2)) + + " ]; skipping"); continue; } - Math::SH::delta (delta, d, lmax); + Math::SH::delta(delta, d, lmax); for (int l = 0; l <= lmax; l += 2) { value_type d_dot_s = 0.0; value_type d_dot_d = 0.0; for (int m = -l; m <= l; ++m) { - size_t i = Math::SH::index (l,m); + size_t i = Math::SH::index(l, m); SH.index(3) = i; value_type s = SH.value(); // TODO: currently this does NOT handle the non-orthonormal basis - d_dot_s += s*delta[i]; - d_dot_d += Math::pow2 (delta[i]); + d_dot_s += s * delta[i]; + d_dot_d += Math::pow2(delta[i]); } value_type val = AL[l] * d_dot_s / d_dot_d; response[Math::ZSH::index(l)] += val; @@ -145,8 +134,7 @@ void run () for (ssize_t n = 0; n < response.size(); ++n) std::cout << response[n] << " "; std::cout << "\n"; - } - else { - File::Matrix::save_vector (response, argument[3]); + } else { + File::Matrix::save_vector(response, argument[3]); } } diff --git a/cmd/shbasis.cpp b/cmd/shbasis.cpp index b0dc8b0005..1522adb459 100644 --- a/cmd/shbasis.cpp +++ b/cmd/shbasis.cpp @@ -27,99 +27,83 @@ #include "math/SH.h" #include "misc/bitset.h" - using namespace MR; using namespace App; - -const char* conversions[] = { "old", "new", "force_oldtonew", "force_newtoold", nullptr }; +const char *conversions[] = {"old", "new", "force_oldtonew", "force_newtoold", nullptr}; enum conv_t { NONE, OLD, NEW, FORCE_OLDTONEW, FORCE_NEWTOOLD }; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Examine the values in spherical harmonic images to estimate (and optionally change) the SH basis used"; DESCRIPTION - + "In previous versions of MRtrix, the convention used for storing spherical harmonic " - "coefficients was a non-orthonormal basis (the m!=0 coefficients were a factor of " - "sqrt(2) too large). This error has been rectified in newer versions of MRtrix, " - "but will cause issues if processing SH data that was generated using an older version " - "of MRtrix (or vice-versa)." - - + "This command provides a mechanism for testing the basis used in storage of image data " - "representing a spherical harmonic series per voxel, and allows the user to forcibly " - "modify the raw image data to conform to the desired basis." + +"In previous versions of MRtrix, the convention used for storing spherical harmonic " + "coefficients was a non-orthonormal basis (the m!=0 coefficients were a factor of " + "sqrt(2) too large). This error has been rectified in newer versions of MRtrix, " + "but will cause issues if processing SH data that was generated using an older version " + "of MRtrix (or vice-versa)." - + "Note that the \"force_*\" conversion choices should only be used in cases where this " - "command has previously been unable to automatically determine the SH basis from the " - "image data, but the user themselves are confident of the SH basis of the data." + + "This command provides a mechanism for testing the basis used in storage of image data " + "representing a spherical harmonic series per voxel, and allows the user to forcibly " + "modify the raw image data to conform to the desired basis." - + Math::SH::encoding_description; + + "Note that the \"force_*\" conversion choices should only be used in cases where this " + "command has previously been unable to automatically determine the SH basis from the " + "image data, but the user themselves are confident of the SH basis of the data." + + Math::SH::encoding_description; ARGUMENTS - + Argument ("SH", "the input image(s) of SH coefficients.").allow_multiple().type_image_in(); - + +Argument("SH", "the input image(s) of SH coefficients.").allow_multiple().type_image_in(); OPTIONS - + Option ("convert", "convert the image data in-place to the desired basis; " - "options are: " + join(conversions, ",") + ".") - + Argument ("mode").type_choice (conversions); - + +Option("convert", + "convert the image data in-place to the desired basis; " + "options are: " + + join(conversions, ",") + ".") + + Argument("mode").type_choice(conversions); } - - - - // Perform a linear regression on the power ratio in each order // Omit l=2 - tends to be abnormally small due to non-isotropic brain-wide fibre distribution -std::pair get_regression (const vector& ratios) -{ +std::pair get_regression(const vector &ratios) { const size_t n = ratios.size() - 1; - Eigen::VectorXf Y (n), b (2); - Eigen::MatrixXf A (n, 2); + Eigen::VectorXf Y(n), b(2); + Eigen::MatrixXf A(n, 2); for (size_t i = 1; i != ratios.size(); ++i) { - Y[i-1] = ratios[i]; - A(i-1,0) = 1.0f; - A(i-1,1) = (2*i)+2; + Y[i - 1] = ratios[i]; + A(i - 1, 0) = 1.0f; + A(i - 1, 1) = (2 * i) + 2; } - b = (A.transpose() * A).ldlt().solve (A.transpose() * Y); - return std::make_pair (b[0], b[1]); + b = (A.transpose() * A).ldlt().solve(A.transpose() * Y); + return std::make_pair(b[0], b[1]); } - - - - -template -void check_and_update (Header& H, const conv_t conversion) -{ +template void check_and_update(Header &H, const conv_t conversion) { const size_t N = H.size(3); - const size_t lmax = Math::SH::LforN (N); + const size_t lmax = Math::SH::LforN(N); // Flag which volumes are m==0 and which are not - BitSet mzero_terms (N, false); + BitSet mzero_terms(N, false); for (size_t l = 2; l <= lmax; l += 2) - mzero_terms[Math::SH::index (l, 0)] = true; + mzero_terms[Math::SH::index(l, 0)] = true; // Open in read-write mode if there's a chance of modification - auto image = H.get_image (true); + auto image = H.get_image(true); // Need to mask out voxels where the DC term is zero - Header header_mask (H); + Header header_mask(H); header_mask.ndim() = 3; header_mask.datatype() = DataType::Bit; - auto mask = Image::scratch (header_mask); + auto mask = Image::scratch(header_mask); { - for (auto i = Loop ("Masking image based on DC term", image, 0, 3) (image, mask); i; ++i) { + for (auto i = Loop("Masking image based on DC term", image, 0, 3)(image, mask); i; ++i) { const value_type value = image.value(); - if (value && std::isfinite (value)) { + if (value && std::isfinite(value)) { mask.value() = true; } else { mask.value() = false; @@ -134,44 +118,44 @@ void check_and_update (Header& H, const conv_t conversion) // volumes independently, and report ratio for each harmonic order std::unique_ptr progress; if (App::log_level > 0 && App::log_level < 2) - progress.reset (new ProgressBar ("Evaluating SH basis of image \"" + H.name() + "\"", N-1)); + progress.reset(new ProgressBar("Evaluating SH basis of image \"" + H.name() + "\"", N - 1)); vector ratios; for (size_t l = 2; l <= lmax; l += 2) { double mzero_sum = 0.0, mnonzero_sum = 0.0; - for (image.index(3) = ssize_t (Math::SH::NforL(l-2)); image.index(3) != ssize_t (Math::SH::NforL(l)); ++image.index(3)) { + for (image.index(3) = ssize_t(Math::SH::NforL(l - 2)); image.index(3) != ssize_t(Math::SH::NforL(l)); + ++image.index(3)) { double sum = 0.0; - for (auto i = Loop (image, 0, 3) (image, mask); i; ++i) { + for (auto i = Loop(image, 0, 3)(image, mask); i; ++i) { if (mask.value()) - sum += Math::pow2 (value_type(image.value())); + sum += Math::pow2(value_type(image.value())); } if (mzero_terms[image.index(3)]) { mzero_sum += sum; - DEBUG ("Volume " + str(image.index(3)) + ", m==0, sum " + str(sum)); + DEBUG("Volume " + str(image.index(3)) + ", m==0, sum " + str(sum)); } else { mnonzero_sum += sum; - DEBUG ("Volume " + str(image.index(3)) + ", m!=0, sum " + str(sum)); + DEBUG("Volume " + str(image.index(3)) + ", m!=0, sum " + str(sum)); } if (progress) - ++*progress; + ++*progress; } const double mnonzero_MSoS = mnonzero_sum / (2.0 * l); const float power_ratio = mnonzero_MSoS / mzero_sum; - ratios.push_back (power_ratio); - - INFO ("SH order " + str(l) + ", ratio of m!=0 to m==0 power: " + str(power_ratio) + - ", m==0 power: " + str (mzero_sum)); + ratios.push_back(power_ratio); + INFO("SH order " + str(l) + ", ratio of m!=0 to m==0 power: " + str(power_ratio) + + ", m==0 power: " + str(mzero_sum)); } if (progress) - progress.reset (nullptr); + progress.reset(nullptr); // First is ratio to be used for SH basis decision, second is gradient of regression - std::pair regression = std::make_pair (0.0f, 0.0f); + std::pair regression = std::make_pair(0.0f, 0.0f); size_t l_for_decision; float power_ratio; @@ -181,36 +165,35 @@ void check_and_update (Header& H, const conv_t conversion) switch (lmax) { - // Lmax == 2: only one order to use - case 2: - power_ratio = ratios.front(); - l_for_decision = 2; - break; - - // Lmax = 4: Use l=4 order to determine SH basis, can't check gradient since l=2 is untrustworthy - case 4: - power_ratio = ratios.back(); - l_for_decision = 4; - break; - - // Lmax = 6: Use l=4 order to determine SH basis, but checking the gradient is not reliable: - // artificially double the threshold so the power ratio at l=6 needs to be substantially - // different to l=4 to throw a warning - case 6: - regression = std::make_pair (ratios[1] - 2*(ratios[2]-ratios[1]), 0.5*(ratios[2]-ratios[1])); - power_ratio = ratios[1]; - l_for_decision = 4; - grad_threshold *= 2.0; - break; - - // Lmax >= 8: Do a linear regression from l=4 to l=lmax, project back to l=0 - // (this is a more reliable quantification on poor data than l=4 alone) - default: - regression = get_regression (ratios); - power_ratio = regression.first; - l_for_decision = 0; - break; - + // Lmax == 2: only one order to use + case 2: + power_ratio = ratios.front(); + l_for_decision = 2; + break; + + // Lmax = 4: Use l=4 order to determine SH basis, can't check gradient since l=2 is untrustworthy + case 4: + power_ratio = ratios.back(); + l_for_decision = 4; + break; + + // Lmax = 6: Use l=4 order to determine SH basis, but checking the gradient is not reliable: + // artificially double the threshold so the power ratio at l=6 needs to be substantially + // different to l=4 to throw a warning + case 6: + regression = std::make_pair(ratios[1] - 2 * (ratios[2] - ratios[1]), 0.5 * (ratios[2] - ratios[1])); + power_ratio = ratios[1]; + l_for_decision = 4; + grad_threshold *= 2.0; + break; + + // Lmax >= 8: Do a linear regression from l=4 to l=lmax, project back to l=0 + // (this is a more reliable quantification on poor data than l=4 alone) + default: + regression = get_regression(ratios); + power_ratio = regression.first; + l_for_decision = 0; + break; } // If the gradient is in fact positive (i.e. power ration increases for larger l), use the @@ -220,115 +203,134 @@ void check_and_update (Header& H, const conv_t conversion) power_ratio = regression.first + (lmax * regression.second); } - DEBUG ("Power ratio for assessing SH basis is " + str(power_ratio) + " as " + (lmax < 8 ? "derived from" : "regressed to") + " l=" + str(l_for_decision)); + DEBUG("Power ratio for assessing SH basis is " + str(power_ratio) + " as " + + (lmax < 8 ? "derived from" : "regressed to") + " l=" + str(l_for_decision)); // Threshold to make decision on what basis the data are currently stored in value_type multiplier = 1.0; - if ((power_ratio > (5.0/3.0)) && (power_ratio < (7.0/3.0))) { + if ((power_ratio > (5.0 / 3.0)) && (power_ratio < (7.0 / 3.0))) { - CONSOLE ("Image \"" + str(H.name()) + "\" appears to be in the old non-orthonormal basis"); + CONSOLE("Image \"" + str(H.name()) + "\" appears to be in the old non-orthonormal basis"); switch (conversion) { - case NONE: break; - case OLD: break; - case NEW: multiplier = Math::sqrt1_2; break; - case FORCE_OLDTONEW: multiplier = Math::sqrt1_2; break; - case FORCE_NEWTOOLD: WARN ("Refusing to convert image \"" + H.name() + "\" from new to old basis, as data appear to already be in the old non-orthonormal basis"); return; + case NONE: + break; + case OLD: + break; + case NEW: + multiplier = Math::sqrt1_2; + break; + case FORCE_OLDTONEW: + multiplier = Math::sqrt1_2; + break; + case FORCE_NEWTOOLD: + WARN("Refusing to convert image \"" + H.name() + + "\" from new to old basis, as data appear to already be in the old non-orthonormal basis"); + return; } grad_threshold *= 2.0; - } else if ((power_ratio > (2.0/3.0)) && (power_ratio < (4.0/3.0))) { + } else if ((power_ratio > (2.0 / 3.0)) && (power_ratio < (4.0 / 3.0))) { - CONSOLE ("Image \"" + str(H.name()) + "\" appears to be in the new orthonormal basis"); + CONSOLE("Image \"" + str(H.name()) + "\" appears to be in the new orthonormal basis"); switch (conversion) { - case NONE: break; - case OLD: multiplier = Math::sqrt2; break; - case NEW: break; - case FORCE_OLDTONEW: WARN ("Refusing to convert image \"" + H.name() + "\" from old to new basis, as data appear to already be in the new orthonormal basis"); return; - case FORCE_NEWTOOLD: multiplier = Math::sqrt2; break; + case NONE: + break; + case OLD: + multiplier = Math::sqrt2; + break; + case NEW: + break; + case FORCE_OLDTONEW: + WARN("Refusing to convert image \"" + H.name() + + "\" from old to new basis, as data appear to already be in the new orthonormal basis"); + return; + case FORCE_NEWTOOLD: + multiplier = Math::sqrt2; + break; } } else { multiplier = 0.0; - WARN ("Cannot make unambiguous decision on SH basis of image \"" + H.name() - + "\" (power ratio " + (lmax < 8 ? "in" : "regressed to") + " " + str(l_for_decision) + " is " + str(power_ratio) + ")"); + WARN("Cannot make unambiguous decision on SH basis of image \"" + H.name() + "\" (power ratio " + + (lmax < 8 ? "in" : "regressed to") + " " + str(l_for_decision) + " is " + str(power_ratio) + ")"); if (conversion == FORCE_OLDTONEW) { - WARN ("Forcing conversion of image \"" + H.name() + "\" from old to new SH basis on user request; however NO GUARANTEE IS PROVIDED on appropriateness of this conversion!"); + WARN("Forcing conversion of image \"" + H.name() + + "\" from old to new SH basis on user request; however NO GUARANTEE IS PROVIDED on appropriateness of this " + "conversion!"); multiplier = Math::sqrt1_2; } else if (conversion == FORCE_NEWTOOLD) { - WARN ("Forcing conversion of image \"" + H.name() + "\" from new to old SH basis on user request; however NO GUARANTEE IS PROVIDED on appropriateness of this conversion!"); + WARN("Forcing conversion of image \"" + H.name() + + "\" from new to old SH basis on user request; however NO GUARANTEE IS PROVIDED on appropriateness of this " + "conversion!"); multiplier = Math::sqrt2; } - } // Decide whether the user needs to be warned about a poor diffusion encoding scheme if (regression.second) - DEBUG ("Gradient of regression is " + str(regression.second) + "; threshold is " + str(grad_threshold)); + DEBUG("Gradient of regression is " + str(regression.second) + "; threshold is " + str(grad_threshold)); if (abs(regression.second) > grad_threshold) { - WARN ("Image \"" + H.name() + "\" may have been derived from poor directional encoding, or have some other underlying data problem"); - WARN ("(m!=0 to m==0 power ratio changing by " + str(2.0*regression.second) + " per even order)"); + WARN("Image \"" + H.name() + + "\" may have been derived from poor directional encoding, or have some other underlying data problem"); + WARN("(m!=0 to m==0 power ratio changing by " + str(2.0 * regression.second) + " per even order)"); } // Adjust the image data in-place if necessary if (multiplier && (multiplier != 1.0)) { - ProgressBar progress ("Modifying SH basis of image \"" + H.name() + "\"", N-1); + ProgressBar progress("Modifying SH basis of image \"" + H.name() + "\"", N - 1); for (image.index(3) = 1; image.index(3) != ssize_t(N); ++image.index(3)) { if (!mzero_terms[image.index(3)]) { - for (auto i = Loop (image, 0, 3) (image); i; ++i) + for (auto i = Loop(image, 0, 3)(image); i; ++i) image.value() *= multiplier; } ++progress; } } else if (multiplier && (conversion != NONE)) { - INFO ("Image \"" + H.name() + "\" already in desired basis; nothing to do"); + INFO("Image \"" + H.name() + "\" already in desired basis; nothing to do"); } - } - - - - - - - - -void run () -{ +void run() { conv_t conversion = NONE; - auto opt = get_options ("convert"); + auto opt = get_options("convert"); if (opt.size()) { switch (int(opt[0][0])) { - case 0: conversion = OLD; break; - case 1: conversion = NEW; break; - case 2: conversion = FORCE_OLDTONEW; break; - case 3: conversion = FORCE_NEWTOOLD; break; - default: assert (0); break; + case 0: + conversion = OLD; + break; + case 1: + conversion = NEW; + break; + case 2: + conversion = FORCE_OLDTONEW; + break; + case 3: + conversion = FORCE_NEWTOOLD; + break; + default: + assert(0); + break; } } for (vector::const_iterator i = argument.begin(); i != argument.end(); ++i) { const std::string path = *i; - Header H = Header::open (path); + Header H = Header::open(path); try { - Math::SH::check (H); - } - catch (Exception& E) { + Math::SH::check(H); + } catch (Exception &E) { E.display(0); continue; } if (H.datatype().bytes() == 4) - check_and_update (H, conversion); + check_and_update(H, conversion); else - check_and_update (H, conversion); - + check_and_update(H, conversion); } - } - diff --git a/cmd/shconv.cpp b/cmd/shconv.cpp index 405caf079b..04fefe6793 100644 --- a/cmd/shconv.cpp +++ b/cmd/shconv.cpp @@ -14,132 +14,116 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "memory.h" -#include "progressbar.h" #include "algo/threaded_loop.h" -#include "image.h" +#include "command.h" #include "file/matrix.h" +#include "image.h" #include "math/SH.h" #include "math/ZSH.h" +#include "memory.h" +#include "progressbar.h" using namespace MR; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) and J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Perform spherical convolution"; DESCRIPTION - + "Provided with matching pairs of response function and ODF images " - "(containing SH coefficients), perform spherical convolution to provide the " - "corresponding SH coefficients of the signal." - + "If multiple pairs of inputs are provided, their contributions will be " - "summed into a single output." - + "If the responses are multi-shell (with one line of coefficients per " - "shell), the output will be a 5-dimensional image, with the SH " - "coefficients of the signal in each shell stored at different indices " - "along the 5th dimension." - + Math::SH::encoding_description; + +"Provided with matching pairs of response function and ODF images " + "(containing SH coefficients), perform spherical convolution to provide the " + "corresponding SH coefficients of the signal." + + "If multiple pairs of inputs are provided, their contributions will be " + "summed into a single output." + + "If the responses are multi-shell (with one line of coefficients per " + "shell), the output will be a 5-dimensional image, with the SH " + "coefficients of the signal in each shell stored at different indices " + "along the 5th dimension." + + Math::SH::encoding_description; DESCRIPTION - + Math::SH::encoding_description; + +Math::SH::encoding_description; ARGUMENTS - + Argument ("odf response", "pairs of input ODF image and corresponding responses").allow_multiple() - + Argument ("SH_out", "the output spherical harmonics coefficients image.").type_image_out (); + +Argument("odf response", "pairs of input ODF image and corresponding responses").allow_multiple() + + Argument("SH_out", "the output spherical harmonics coefficients image.").type_image_out(); OPTIONS - + DataType::options() - + Stride::Options; + +DataType::options() + Stride::Options; } - - using value_type = float; - -class SConvFunctor { - public: - SConvFunctor (const vector& responses, vector>& inputs) : - responses (responses), - inputs (inputs) { } - - void operator() (Image& output) - { - for (size_t n = 0; n < inputs.size(); ++n) { - assign_pos_of (output, 0, 3).to (inputs[n]); - in = inputs[n].row (3); - for (ssize_t s = 0; s < responses[n].rows(); ++s) { - Math::SH::sconv (out, responses[n].row(s), in); - if (output.ndim() > 4) - output.index(4) = s; - for (ssize_t k = 0; k < out.size(); ++k) { - output.index(3) = k; - output.value() += out[k]; - } +class SConvFunctor { +public: + SConvFunctor(const vector &responses, vector> &inputs) + : responses(responses), inputs(inputs) {} + + void operator()(Image &output) { + for (size_t n = 0; n < inputs.size(); ++n) { + assign_pos_of(output, 0, 3).to(inputs[n]); + in = inputs[n].row(3); + for (ssize_t s = 0; s < responses[n].rows(); ++s) { + Math::SH::sconv(out, responses[n].row(s), in); + if (output.ndim() > 4) + output.index(4) = s; + for (ssize_t k = 0; k < out.size(); ++k) { + output.index(3) = k; + output.value() += out[k]; } } } + } - protected: - const vector& responses; - vector> inputs; - Eigen::VectorXd in, out; - +protected: + const vector &responses; + vector> inputs; + Eigen::VectorXd in, out; }; - - - - - -void run() -{ +void run() { if (!(argument.size() & size_t(1U))) - throw Exception ("unexpected number of arguments"); + throw Exception("unexpected number of arguments"); - vector> inputs ((argument.size() - 1) / 2); - vector responses (inputs.size()); + vector> inputs((argument.size() - 1) / 2); + vector responses(inputs.size()); size_t lmax = 0; for (size_t n = 0; n < inputs.size(); ++n) { - inputs[n] = Image::open (argument[2*n]); - Math::SH::check (inputs[n]); + inputs[n] = Image::open(argument[2 * n]); + Math::SH::check(inputs[n]); if (inputs[n].ndim() > 4 && inputs[n].size(4) > 1) - throw Exception ("input ODF contains more than 4 dimensions"); + throw Exception("input ODF contains more than 4 dimensions"); - responses[n] = File::Matrix::load_matrix (argument[2*n+1]); - responses[n].conservativeResizeLike (Eigen::MatrixXd::Zero (responses[n].rows(), Math::ZSH::NforL (Math::SH::LforN (inputs[n].size (3))))); - lmax = std::max (Math::ZSH::LforN (responses[n].cols()), lmax); + responses[n] = File::Matrix::load_matrix(argument[2 * n + 1]); + responses[n].conservativeResizeLike( + Eigen::MatrixXd::Zero(responses[n].rows(), Math::ZSH::NforL(Math::SH::LforN(inputs[n].size(3))))); + lmax = std::max(Math::ZSH::LforN(responses[n].cols()), lmax); for (ssize_t k = 0; k < responses[n].rows(); ++k) - responses[n].row(k) = Math::ZSH::ZSH2RH (responses[n].row(k)); + responses[n].row(k) = Math::ZSH::ZSH2RH(responses[n].row(k)); if (n) { if (responses[n].rows() != responses[0].rows()) - throw Exception ("number of shells differs between response files"); - check_dimensions (inputs[n], inputs[0], 0, 3); + throw Exception("number of shells differs between response files"); + check_dimensions(inputs[n], inputs[0], 0, 3); } } - - Header header (inputs[0]); + Header header(inputs[0]); if (responses[0].rows() > 1) { header.ndim() = 5; header.size(4) = responses[0].rows(); - } - else + } else header.ndim() = 4; - header.size(3) = Math::SH::NforL (lmax); - Stride::set_from_command_line (header, Stride::contiguous_along_axis (3, header)); - header.datatype() = DataType::from_command_line (DataType::Float32); + header.size(3) = Math::SH::NforL(lmax); + Stride::set_from_command_line(header, Stride::contiguous_along_axis(3, header)); + header.datatype() = DataType::from_command_line(DataType::Float32); - auto output = Image::create (argument[argument.size()-1], header); + auto output = Image::create(argument[argument.size() - 1], header); - SConvFunctor sconv (responses, inputs); - ThreadedLoop ("performing spherical convolution", inputs[0], 0, 3).run (sconv, output); + SConvFunctor sconv(responses, inputs); + ThreadedLoop("performing spherical convolution", inputs[0], 0, 3).run(sconv, output); } diff --git a/cmd/shview.cpp b/cmd/shview.cpp index f0b15f8539..54d04f6fe1 100644 --- a/cmd/shview.cpp +++ b/cmd/shview.cpp @@ -14,53 +14,44 @@ * For more details, see http://www.mrtrix.org/. */ -#include "gui/gui.h" #include "command.h" -#include "progressbar.h" #include "file/path.h" -#include "math/SH.h" +#include "gui/gui.h" +#include "gui/shview/file_open.h" #include "gui/shview/icons.h" #include "gui/shview/render_window.h" -#include "gui/shview/file_open.h" - +#include "math/SH.h" +#include "progressbar.h" using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "View spherical harmonics surface plots"; ARGUMENTS - + Argument ("coefs", - "a text file containing the even order spherical harmonics coefficients to display.") - .optional() - .type_file_in(); + +Argument("coefs", "a text file containing the even order spherical harmonics coefficients to display.") + .optional() + .type_file_in(); OPTIONS - + Option ("response", - "assume SH coefficients file only contains m=0 terms (zonal harmonics). " - "Used to display the response function as produced by estimate_response"); + +Option("response", + "assume SH coefficients file only contains m=0 terms (zonal harmonics). " + "Used to display the response function as produced by estimate_response"); REQUIRES_AT_LEAST_ONE_ARGUMENT = false; } - - - - -void run () -{ - GUI::DWI::Window window (get_options ("response").size()); +void run() { + GUI::DWI::Window window(get_options("response").size()); if (argument.size()) - window.set_values (std::string (argument[0])); + window.set_values(std::string(argument[0])); window.show(); if (qApp->exec()) - throw Exception ("error running Qt application"); + throw Exception("error running Qt application"); } - diff --git a/cmd/tck2connectome.cpp b/cmd/tck2connectome.cpp index d130431f35..1c33fdad28 100644 --- a/cmd/tck2connectome.cpp +++ b/cmd/tck2connectome.cpp @@ -21,16 +21,15 @@ #include "thread_queue.h" #include "types.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/connectome/connectome.h" -#include "dwi/tractography/connectome/metric.h" #include "dwi/tractography/connectome/mapper.h" #include "dwi/tractography/connectome/matrix.h" +#include "dwi/tractography/connectome/metric.h" #include "dwi/tractography/connectome/tck2nodes.h" - +#include "dwi/tractography/file.h" +#include "dwi/tractography/mapping/loader.h" +#include "dwi/tractography/properties.h" +#include "dwi/tractography/weights.h" using namespace MR; using namespace App; @@ -38,163 +37,157 @@ using namespace MR::DWI; using namespace MR::DWI::Tractography; using namespace MR::DWI::Tractography::Connectome; - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Generate a connectome matrix from a streamlines file and a node parcellation image"; - EXAMPLES - + Example ("Default usage", - "tck2connectome tracks.tck nodes.mif connectome.csv -tck_weights_in weights.csv -out_assignments assignments.txt", - "By default, the metric of connectivity quantified in the connectome matrix is the " - "number of streamlines; or, if tcksift2 is used, the sum of streamline weights via the " - "-tck_weights_in option. Use of the -out_assignments option is recommended as this " - "enables subsequent use of the connectome2tck command.") - - + Example ("Generate a matrix consisting of the mean streamline length between each node pair", - "tck2connectome tracks.tck nodes.mif distances.csv -scale_length -stat_edge mean", - "By multiplying the contribution of each streamline to the connectome by the length " - "of that streamline, and then, for each edge, computing the mean value across the " - "contributing streamlines, one obtains a matrix where the value in each entry is the " - "mean length across those streamlines belonging to that edge.") - - + Example ("Generate a connectome matrix where the value of connectivity is the \"mean FA\"", - "tcksample tracks.tck FA.mif mean_FA_per_streamline.csv -stat_tck mean; " - "tck2connectome tracks.tck nodes.mif mean_FA_connectome.csv -scale_file mean_FA_per_streamline.csv -stat_edge mean", - "Here, a connectome matrix that is \"weighted by FA\" is generated in multiple steps: " - "firstly, for each streamline, the value of the underlying FA image is sampled at each " - "vertex, and the mean of these values is calculated to produce a single scalar value of " - "\"mean FA\" per streamline; then, as each streamline is assigned to nodes within the " - "connectome, the magnitude of the contribution of that streamline to the matrix is " - "multiplied by the mean FA value calculated prior for that streamline; finally, for " - "each connectome edge, across the values of \"mean FA\" that were contributed by all " - "of the streamlines assigned to that particular edge, the mean value is calculated.") - - + Example ("Generate the connectivity fingerprint for streamlines seeded from a particular region", - "tck2connectome fixed_seed_tracks.tck nodes.mif fingerprint.csv -vector", - "This usage assumes that the streamlines being provided to the command have all been " - "seeded from the (effectively) same location, and as such, only the endpoint of each " - "streamline (not their starting point) is assigned based on the provided parcellation " - "image. Accordingly, the output file contains only a vector of connectivity values " - "rather than a matrix, since each streamline is assigned to only one node rather than two."); - + +Example( + "Default usage", + "tck2connectome tracks.tck nodes.mif connectome.csv -tck_weights_in weights.csv -out_assignments assignments.txt", + "By default, the metric of connectivity quantified in the connectome matrix is the " + "number of streamlines; or, if tcksift2 is used, the sum of streamline weights via the " + "-tck_weights_in option. Use of the -out_assignments option is recommended as this " + "enables subsequent use of the connectome2tck command.") + + + Example("Generate a matrix consisting of the mean streamline length between each node pair", + "tck2connectome tracks.tck nodes.mif distances.csv -scale_length -stat_edge mean", + "By multiplying the contribution of each streamline to the connectome by the length " + "of that streamline, and then, for each edge, computing the mean value across the " + "contributing streamlines, one obtains a matrix where the value in each entry is the " + "mean length across those streamlines belonging to that edge.") + + + Example("Generate a connectome matrix where the value of connectivity is the \"mean FA\"", + "tcksample tracks.tck FA.mif mean_FA_per_streamline.csv -stat_tck mean; " + "tck2connectome tracks.tck nodes.mif mean_FA_connectome.csv -scale_file mean_FA_per_streamline.csv " + "-stat_edge mean", + "Here, a connectome matrix that is \"weighted by FA\" is generated in multiple steps: " + "firstly, for each streamline, the value of the underlying FA image is sampled at each " + "vertex, and the mean of these values is calculated to produce a single scalar value of " + "\"mean FA\" per streamline; then, as each streamline is assigned to nodes within the " + "connectome, the magnitude of the contribution of that streamline to the matrix is " + "multiplied by the mean FA value calculated prior for that streamline; finally, for " + "each connectome edge, across the values of \"mean FA\" that were contributed by all " + "of the streamlines assigned to that particular edge, the mean value is calculated.") + + + Example("Generate the connectivity fingerprint for streamlines seeded from a particular region", + "tck2connectome fixed_seed_tracks.tck nodes.mif fingerprint.csv -vector", + "This usage assumes that the streamlines being provided to the command have all been " + "seeded from the (effectively) same location, and as such, only the endpoint of each " + "streamline (not their starting point) is assigned based on the provided parcellation " + "image. Accordingly, the output file contains only a vector of connectivity values " + "rather than a matrix, since each streamline is assigned to only one node rather than two."); ARGUMENTS - + Argument ("tracks_in", "the input track file").type_tracks_in() - + Argument ("nodes_in", "the input node parcellation image").type_image_in() - + Argument ("connectome_out", "the output .csv file containing edge weights").type_file_out(); - + +Argument("tracks_in", "the input track file").type_tracks_in() + + Argument("nodes_in", "the input node parcellation image").type_image_in() + + Argument("connectome_out", "the output .csv file containing edge weights").type_file_out(); OPTIONS - + MR::DWI::Tractography::Connectome::AssignmentOptions - + MR::DWI::Tractography::Connectome::MetricOptions - + MR::Connectome::MatrixOutputOptions + +MR::DWI::Tractography::Connectome::AssignmentOptions + MR::DWI::Tractography::Connectome::MetricOptions + + MR::Connectome::MatrixOutputOptions - + OptionGroup ("Other options for tck2connectome") + + OptionGroup("Other options for tck2connectome") - + MR::DWI::Tractography::Connectome::EdgeStatisticOption + + MR::DWI::Tractography::Connectome::EdgeStatisticOption - + Tractography::TrackWeightsInOption + + Tractography::TrackWeightsInOption - + Option ("keep_unassigned", "By default, the program discards the information regarding those streamlines that are not successfully assigned to a node pair. " - "Set this option to keep these values (will be the first row/column in the output matrix)") + + Option("keep_unassigned", + "By default, the program discards the information regarding those streamlines that are not successfully " + "assigned to a node pair. " + "Set this option to keep these values (will be the first row/column in the output matrix)") - + Option ("out_assignments", "output the node assignments of each streamline to a file; " - "this can be used subsequently e.g. by the command connectome2tck") - + Argument ("path").type_file_out() + + Option("out_assignments", + "output the node assignments of each streamline to a file; " + "this can be used subsequently e.g. by the command connectome2tck") + + Argument("path").type_file_out() - + Option ("vector", "output a vector representing connectivities from a given seed point to target nodes, " - "rather than a matrix of node-node connectivities"); + + Option("vector", + "output a vector representing connectivities from a given seed point to target nodes, " + "rather than a matrix of node-node connectivities"); REFERENCES - + "If using the default streamline-parcel assignment mechanism (or -assignment_radial_search option): " // Internal - "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " - "The effects of SIFT on the reproducibility and biological accuracy of the structural connectome. " - "NeuroImage, 2015, 104, 253-265" - - + "If using -scale_invlength or -scale_invnodevol options: " - "Hagmann, P.; Cammoun, L.; Gigandet, X.; Meuli, R.; Honey, C.; Wedeen, V. & Sporns, O. " - "Mapping the Structural Core of Human Cerebral Cortex. " - "PLoS Biology 6(7), e159"; - + +"If using the default streamline-parcel assignment mechanism (or -assignment_radial_search option): " // Internal + "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " + "The effects of SIFT on the reproducibility and biological accuracy of the structural connectome. " + "NeuroImage, 2015, 104, 253-265" + + + "If using -scale_invlength or -scale_invnodevol options: " + "Hagmann, P.; Cammoun, L.; Gigandet, X.; Meuli, R.; Honey, C.; Wedeen, V. & Sporns, O. " + "Mapping the Structural Core of Human Cerebral Cortex. " + "PLoS Biology 6(7), e159"; } - - template -void execute (Image& node_image, const node_t max_node_index, const std::set& missing_nodes) -{ +void execute(Image &node_image, const node_t max_node_index, const std::set &missing_nodes) { // Are we generating a matrix or a vector? - const bool vector_output = get_options ("vector").size(); + const bool vector_output = get_options("vector").size(); // Do we need to keep track of the nodes to which each streamline is // assigned, or would it be a waste of memory? - const bool track_assignments = get_options ("out_assignments").size(); + const bool track_assignments = get_options("out_assignments").size(); // Get the metric, assignment mechanism & per-edge statistic for connectome construction Metric metric; - Tractography::Connectome::setup_metric (metric, node_image); - std::unique_ptr tck2nodes (load_assignment_mode (node_image)); - auto opt = get_options ("stat_edge"); + Tractography::Connectome::setup_metric(metric, node_image); + std::unique_ptr tck2nodes(load_assignment_mode(node_image)); + auto opt = get_options("stat_edge"); const stat_edge statistic = opt.size() ? stat_edge(int(opt[0][0])) : stat_edge::SUM; // Prepare for reading the track data Tractography::Properties properties; - Tractography::Reader reader (argument[0], properties); + Tractography::Reader reader(argument[0], properties); // Initialise classes in preparation for multi-threading - Mapping::TrackLoader loader (reader, properties["count"].empty() ? 0 : to(properties["count"]), "Constructing connectome"); - Tractography::Connectome::Mapper mapper (*tck2nodes, metric); - Tractography::Connectome::Matrix connectome (max_node_index, statistic, vector_output, track_assignments); + Mapping::TrackLoader loader( + reader, properties["count"].empty() ? 0 : to(properties["count"]), "Constructing connectome"); + Tractography::Connectome::Mapper mapper(*tck2nodes, metric); + Tractography::Connectome::Matrix connectome(max_node_index, statistic, vector_output, track_assignments); // Multi-threaded connectome construction if (tck2nodes->provides_pair()) { - Thread::run_queue ( - loader, - Thread::batch (Tractography::Streamline()), - Thread::multi (mapper), - Thread::batch (Mapped_track_nodepair()), - connectome); + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline()), + Thread::multi(mapper), + Thread::batch(Mapped_track_nodepair()), + connectome); } else { - Thread::run_queue ( - loader, - Thread::batch (Tractography::Streamline()), - Thread::multi (mapper), - Thread::batch (Mapped_track_nodelist()), - connectome); + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline()), + Thread::multi(mapper), + Thread::batch(Mapped_track_nodelist()), + connectome); } connectome.finalize(); - connectome.error_check (missing_nodes); + connectome.error_check(missing_nodes); - connectome.save (argument[2], get_options ("keep_unassigned").size(), get_options ("symmetric").size(), get_options ("zero_diagonal").size()); + connectome.save(argument[2], + get_options("keep_unassigned").size(), + get_options("symmetric").size(), + get_options("zero_diagonal").size()); - opt = get_options ("out_assignments"); + opt = get_options("out_assignments"); if (opt.size()) - connectome.write_assignments (opt[0][0]); + connectome.write_assignments(opt[0][0]); } - - -void run () -{ - auto node_header = Header::open (argument[1]); - MR::Connectome::check (node_header); +void run() { + auto node_header = Header::open(argument[1]); + MR::Connectome::check(node_header); auto node_image = node_header.get_image(); // First, find out how many segmented nodes there are, so the matrix can be pre-allocated // Also check for node volume for all nodes - vector node_volumes (1, 0); + vector node_volumes(1, 0); node_t max_node_index = 0; - for (auto i = Loop (node_image, 0, 3) (node_image); i; ++i) { + for (auto i = Loop(node_image, 0, 3)(node_image); i; ++i) { if (node_image.value() > max_node_index) { max_node_index = node_image.value(); - node_volumes.resize (max_node_index + 1, 0); + node_volumes.resize(max_node_index + 1, 0); } ++node_volumes[node_image.value()]; } @@ -202,22 +195,23 @@ void run () std::set missing_nodes; for (size_t i = 1; i != node_volumes.size(); ++i) { if (!node_volumes[i]) - missing_nodes.insert (i); + missing_nodes.insert(i); } if (missing_nodes.size()) { - WARN ("The following nodes are missing from the parcellation image:"); + WARN("The following nodes are missing from the parcellation image:"); std::set::iterator i = missing_nodes.begin(); std::string list = str(*i); for (++i; i != missing_nodes.end(); ++i) list += ", " + str(*i); - WARN (list); - WARN ("(This may indicate poor parcellation image preparation, use of incorrect or incomplete LUT file(s) in labelconvert, or very poor registration)"); + WARN(list); + WARN("(This may indicate poor parcellation image preparation, use of incorrect or incomplete LUT file(s) in " + "labelconvert, or very poor registration)"); } if (max_node_index >= node_count_ram_limit) { - INFO ("Very large number of nodes detected; using single-precision floating-point storage"); - execute (node_image, max_node_index, missing_nodes); + INFO("Very large number of nodes detected; using single-precision floating-point storage"); + execute(node_image, max_node_index, missing_nodes); } else { - execute (node_image, max_node_index, missing_nodes); + execute(node_image, max_node_index, missing_nodes); } } diff --git a/cmd/tck2fixel.cpp b/cmd/tck2fixel.cpp index 8733017a24..0aaf473370 100644 --- a/cmd/tck2fixel.cpp +++ b/cmd/tck2fixel.cpp @@ -14,20 +14,19 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "progressbar.h" #include "algo/loop.h" +#include "command.h" #include "image.h" +#include "progressbar.h" #include "fixel/fixel.h" #include "fixel/helpers.h" #include "fixel/loop.h" -#include "dwi/tractography/mapping/mapper.h" #include "dwi/tractography/mapping/loader.h" +#include "dwi/tractography/mapping/mapper.h" #include "dwi/tractography/mapping/writer.h" - using namespace MR; using namespace App; @@ -35,157 +34,148 @@ using Fixel::index_type; #define DEFAULT_ANGLE_THRESHOLD 45.0 +class TrackProcessor { + +public: + using SetVoxelDir = DWI::Tractography::Mapping::SetVoxelDir; + + TrackProcessor(Image &fixel_indexer, + const vector &fixel_directions, + vector &fixel_TDI, + const float angular_threshold) + : fixel_indexer(fixel_indexer), + fixel_directions(fixel_directions), + fixel_TDI(fixel_TDI), + angular_threshold_dp(std::cos(angular_threshold * (Math::pi / 180.0))) {} + + bool operator()(const SetVoxelDir &in) { + // For each voxel tract tangent, assign to a fixel + vector tract_fixel_indices; + for (SetVoxelDir::const_iterator i = in.begin(); i != in.end(); ++i) { + assign_pos_of(*i).to(fixel_indexer); + fixel_indexer.index(3) = 0; + index_type num_fibres = fixel_indexer.value(); + if (num_fibres > 0) { + fixel_indexer.index(3) = 1; + index_type first_index = fixel_indexer.value(); + index_type last_index = first_index + num_fibres; + index_type closest_fixel_index = 0; + float largest_dp = 0.0; + const Eigen::Vector3d dir(i->get_dir().normalized()); + for (index_type j = first_index; j < last_index; ++j) { + const float dp = abs(dir.dot(fixel_directions[j])); + if (dp > largest_dp) { + largest_dp = dp; + closest_fixel_index = j; + } + } + if (largest_dp > angular_threshold_dp) { + tract_fixel_indices.push_back(closest_fixel_index); + fixel_TDI[closest_fixel_index]++; + } + } + } + return true; + } -class TrackProcessor { - - public: - - using SetVoxelDir = DWI::Tractography::Mapping::SetVoxelDir; - - TrackProcessor (Image& fixel_indexer, - const vector& fixel_directions, - vector& fixel_TDI, - const float angular_threshold): - fixel_indexer (fixel_indexer) , - fixel_directions (fixel_directions), - fixel_TDI (fixel_TDI), - angular_threshold_dp (std::cos (angular_threshold * (Math::pi/180.0))) { } - - - bool operator () (const SetVoxelDir& in) { - // For each voxel tract tangent, assign to a fixel - vector tract_fixel_indices; - for (SetVoxelDir::const_iterator i = in.begin(); i != in.end(); ++i) { - assign_pos_of (*i).to (fixel_indexer); - fixel_indexer.index(3) = 0; - index_type num_fibres = fixel_indexer.value(); - if (num_fibres > 0) { - fixel_indexer.index(3) = 1; - index_type first_index = fixel_indexer.value(); - index_type last_index = first_index + num_fibres; - index_type closest_fixel_index = 0; - float largest_dp = 0.0; - const Eigen::Vector3d dir (i->get_dir().normalized()); - for (index_type j = first_index; j < last_index; ++j) { - const float dp = abs (dir.dot (fixel_directions[j])); - if (dp > largest_dp) { - largest_dp = dp; - closest_fixel_index = j; - } - } - if (largest_dp > angular_threshold_dp) { - tract_fixel_indices.push_back (closest_fixel_index); - fixel_TDI[closest_fixel_index]++; - } - } - } - return true; - } - - - private: - Image fixel_indexer; - const vector& fixel_directions; - vector& fixel_TDI; - const float angular_threshold_dp; +private: + Image fixel_indexer; + const vector &fixel_directions; + vector &fixel_TDI; + const float angular_threshold_dp; }; - -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Compute a fixel TDI map from a tractogram"; DESCRIPTION - + Fixel::format_description; + +Fixel::format_description; ARGUMENTS - + Argument ("tracks", "the input tracks.").type_tracks_in() - + Argument ("fixel_folder_in", "the input fixel folder. Used to define the fixels and their directions").type_directory_in() - + Argument ("fixel_folder_out", "the fixel folder to which the output will be written. This can be the same as the input folder if desired").type_text() - + Argument ("fixel_data_out", "the name of the fixel data image.").type_text(); + +Argument("tracks", "the input tracks.").type_tracks_in() + + Argument("fixel_folder_in", "the input fixel folder. Used to define the fixels and their directions") + .type_directory_in() + + Argument( + "fixel_folder_out", + "the fixel folder to which the output will be written. This can be the same as the input folder if desired") + .type_text() + + Argument("fixel_data_out", "the name of the fixel data image.").type_text(); OPTIONS - + Option ("angle", "the max angle threshold for assigning streamline tangents to fixels (Default: " + str(DEFAULT_ANGLE_THRESHOLD, 2) + " degrees)") - + Argument ("value").type_float (0.0, 90.0); + +Option("angle", + "the max angle threshold for assigning streamline tangents to fixels (Default: " + + str(DEFAULT_ANGLE_THRESHOLD, 2) + " degrees)") + + Argument("value").type_float(0.0, 90.0); } - - template -void write_fixel_output (const std::string& filename, - const VectorType& data, - const Header& header) -{ - auto output = Image::create (filename, header); +void write_fixel_output(const std::string &filename, const VectorType &data, const Header &header) { + auto output = Image::create(filename, header); for (size_t i = 0; i < data.size(); ++i) { output.index(0) = i; output.value() = data[i]; } } - - -void run () -{ +void run() { const std::string input_fixel_folder = argument[1]; - Header index_header = Fixel::find_index_header (input_fixel_folder); + Header index_header = Fixel::find_index_header(input_fixel_folder); auto index_image = index_header.get_image(); - const index_type num_fixels = Fixel::get_number_of_fixels (index_header); + const index_type num_fixels = Fixel::get_number_of_fixels(index_header); - const float angular_threshold = get_option_value ("angle", DEFAULT_ANGLE_THRESHOLD); + const float angular_threshold = get_option_value("angle", DEFAULT_ANGLE_THRESHOLD); - vector positions (num_fixels); - vector directions (num_fixels); + vector positions(num_fixels); + vector directions(num_fixels); const std::string output_fixel_folder = argument[2]; - Fixel::copy_index_and_directions_file (input_fixel_folder, output_fixel_folder); + Fixel::copy_index_and_directions_file(input_fixel_folder, output_fixel_folder); { - auto directions_data = Fixel::find_directions_header (input_fixel_folder).get_image().with_direct_io(); + auto directions_data = Fixel::find_directions_header(input_fixel_folder).get_image().with_direct_io(); // Load template fixel directions - Transform image_transform (index_image); - for (auto i = Loop ("loading template fixel directions and positions", index_image, 0, 3)(index_image); i; ++i) { - const Eigen::Vector3d vox ((default_type)index_image.index(0), (default_type)index_image.index(1), (default_type)index_image.index(2)); + Transform image_transform(index_image); + for (auto i = Loop("loading template fixel directions and positions", index_image, 0, 3)(index_image); i; ++i) { + const Eigen::Vector3d vox( + (default_type)index_image.index(0), (default_type)index_image.index(1), (default_type)index_image.index(2)); index_image.index(3) = 1; index_type offset = index_image.value(); index_type fixel_index = 0; - for (auto f = Fixel::Loop (index_image) (directions_data); f; ++f, ++fixel_index) { + for (auto f = Fixel::Loop(index_image)(directions_data); f; ++f, ++fixel_index) { directions[offset + fixel_index] = directions_data.row(1); positions[offset + fixel_index] = image_transform.voxel2scanner * vox; } } } - vector fixel_TDI (num_fixels, 0.0); + vector fixel_TDI(num_fixels, 0.0); const std::string track_filename = argument[0]; DWI::Tractography::Properties properties; - DWI::Tractography::Reader track_file (track_filename, properties); + DWI::Tractography::Reader track_file(track_filename, properties); // Read in tracts, and compute whole-brain fixel-fixel connectivity - const size_t num_tracks = properties["count"].empty() ? 0 : to (properties["count"]); + const size_t num_tracks = properties["count"].empty() ? 0 : to(properties["count"]); if (!num_tracks) - throw Exception ("no tracks found in input file"); + throw Exception("no tracks found in input file"); { using SetVoxelDir = DWI::Tractography::Mapping::SetVoxelDir; - DWI::Tractography::Mapping::TrackLoader loader (track_file, num_tracks, "mapping tracks to fixels"); - DWI::Tractography::Mapping::TrackMapperBase mapper (index_image); - mapper.set_upsample_ratio (DWI::Tractography::Mapping::determine_upsample_ratio (index_header, properties, 0.333f)); - mapper.set_use_precise_mapping (true); - TrackProcessor tract_processor (index_image, directions, fixel_TDI, angular_threshold); - Thread::run_queue ( - loader, - Thread::batch (DWI::Tractography::Streamline()), - mapper, - Thread::batch (SetVoxelDir()), - tract_processor); + DWI::Tractography::Mapping::TrackLoader loader(track_file, num_tracks, "mapping tracks to fixels"); + DWI::Tractography::Mapping::TrackMapperBase mapper(index_image); + mapper.set_upsample_ratio(DWI::Tractography::Mapping::determine_upsample_ratio(index_header, properties, 0.333f)); + mapper.set_use_precise_mapping(true); + TrackProcessor tract_processor(index_image, directions, fixel_TDI, angular_threshold); + Thread::run_queue(loader, + Thread::batch(DWI::Tractography::Streamline()), + mapper, + Thread::batch(SetVoxelDir()), + tract_processor); } track_file.close(); - Header output_header (Fixel::data_header_from_index (index_image)); - - write_fixel_output (Path::join (output_fixel_folder, argument[3]), fixel_TDI, output_header); + Header output_header(Fixel::data_header_from_index(index_image)); + write_fixel_output(Path::join(output_fixel_folder, argument[3]), fixel_TDI, output_header); } diff --git a/cmd/tckconvert.cpp b/cmd/tckconvert.cpp index 3a2f591d0e..5260cfbef3 100644 --- a/cmd/tckconvert.cpp +++ b/cmd/tckconvert.cpp @@ -14,15 +14,15 @@ * For more details, see http://www.mrtrix.org/. */ -#include -#include #include "command.h" +#include "dwi/tractography/file.h" +#include "dwi/tractography/properties.h" #include "file/matrix.h" #include "file/name_parser.h" #include "file/ofstream.h" -#include "dwi/tractography/file.h" -#include "dwi/tractography/properties.h" #include "raw.h" +#include +#include using namespace MR; using namespace App; @@ -30,748 +30,667 @@ using namespace MR::DWI::Tractography; using namespace MR::Raw; using namespace MR::ByteOrder; -void usage () -{ +void usage() { AUTHOR = "Daan Christiaens (daan.christiaens@kcl.ac.uk), " - "J-Donald Tournier (jdtournier@gmail.com), " - "Philip Broser (philip.broser@me.com), " - "Daniel Blezek (daniel.blezek@gmail.com)."; + "J-Donald Tournier (jdtournier@gmail.com), " + "Philip Broser (philip.broser@me.com), " + "Daniel Blezek (daniel.blezek@gmail.com)."; SYNOPSIS = "Convert between different track file formats"; DESCRIPTION - + "The program currently supports MRtrix .tck files (input/output), " - "ascii text files (input/output), VTK polydata files (input/output), " - "and RenderMan RIB (export only)." + +"The program currently supports MRtrix .tck files (input/output), " + "ascii text files (input/output), VTK polydata files (input/output), " + "and RenderMan RIB (export only)." - + "Note that ascii files will be stored with one streamline per numbered file. " - "To support this, the command will use the multi-file numbering syntax, " - "where square brackets denote the position of the numbering for the files, " - "for example:" + + "Note that ascii files will be stored with one streamline per numbered file. " + "To support this, the command will use the multi-file numbering syntax, " + "where square brackets denote the position of the numbering for the files, " + "for example:" - + "$ tckconvert input.tck output-'[]'.txt" + + "$ tckconvert input.tck output-'[]'.txt" - + "will produce files named output-0000.txt, output-0001.txt, output-0002.txt, ..."; + + "will produce files named output-0000.txt, output-0001.txt, output-0002.txt, ..."; ARGUMENTS - + Argument ("input", "the input track file.").type_various () - + Argument ("output", "the output track file.").type_file_out (); + +Argument("input", "the input track file.").type_various() + + Argument("output", "the output track file.").type_file_out(); OPTIONS - + Option ("scanner2voxel", - "if specified, the properties of this image will be used to convert " - "track point positions from real (scanner) coordinates into voxel coordinates.") - + Argument ("reference").type_image_in () - - + Option ("scanner2image", - "if specified, the properties of this image will be used to convert " - "track point positions from real (scanner) coordinates into image coordinates (in mm).") - + Argument ("reference").type_image_in () + +Option("scanner2voxel", + "if specified, the properties of this image will be used to convert " + "track point positions from real (scanner) coordinates into voxel coordinates.") + + Argument("reference").type_image_in() - + Option ("voxel2scanner", - "if specified, the properties of this image will be used to convert " - "track point positions from voxel coordinates into real (scanner) coordinates.") - + Argument ("reference").type_image_in () + + Option("scanner2image", + "if specified, the properties of this image will be used to convert " + "track point positions from real (scanner) coordinates into image coordinates (in mm).") + + Argument("reference").type_image_in() - + Option ("image2scanner", - "if specified, the properties of this image will be used to convert " - "track point positions from image coordinates (in mm) into real (scanner) coordinates.") - + Argument ("reference").type_image_in () + + Option("voxel2scanner", + "if specified, the properties of this image will be used to convert " + "track point positions from voxel coordinates into real (scanner) coordinates.") + + Argument("reference").type_image_in() - + OptionGroup ("Options specific to PLY writer") + + Option("image2scanner", + "if specified, the properties of this image will be used to convert " + "track point positions from image coordinates (in mm) into real (scanner) coordinates.") + + Argument("reference").type_image_in() - + Option ("sides", "number of sides for streamlines") - + Argument("sides").type_integer(3,15) + + OptionGroup("Options specific to PLY writer") - + Option ("increment", "generate streamline points at every (increment) points") - + Argument("increment").type_integer(1) + + Option("sides", "number of sides for streamlines") + Argument("sides").type_integer(3, 15) - + OptionGroup ("Options specific to RIB writer") + + Option("increment", "generate streamline points at every (increment) points") + + Argument("increment").type_integer(1) - + Option ("dec", "add DEC as a primvar") + + OptionGroup("Options specific to RIB writer") - + OptionGroup ("Options for both PLY and RIB writer") + + Option("dec", "add DEC as a primvar") - + Option ("radius", "radius of the streamlines") - + Argument("radius").type_float(0.0f) + + OptionGroup("Options for both PLY and RIB writer") - + OptionGroup ("Options specific to VTK writer") + + Option("radius", "radius of the streamlines") + Argument("radius").type_float(0.0f) - + Option ("ascii", "write an ASCII VTK file (binary by default)"); + + OptionGroup("Options specific to VTK writer") + + Option("ascii", "write an ASCII VTK file (binary by default)"); } +class VTKWriter : public WriterInterface { +public: + VTKWriter(const std::string &file, bool write_ascii = true) + : VTKout(file, std::ios::binary), write_ascii(write_ascii) { + // create and write header of VTK output file: + VTKout << "# vtk DataFile Version 3.0\n" + "Data values for Tracks\n"; + if (write_ascii) { + VTKout << "ASCII\n"; + } else { + VTKout << "BINARY\n"; + } + VTKout << "DATASET POLYDATA\n" + "POINTS "; + // keep track of offset to write proper value later: + offset_num_points = VTKout.tellp(); + VTKout << "XXXXXXXXXX float\n"; + } - - - - - -class VTKWriter: public WriterInterface { - public: - VTKWriter(const std::string& file, bool write_ascii = true) : - VTKout (file, std::ios::binary), write_ascii(write_ascii) { - // create and write header of VTK output file: - VTKout << - "# vtk DataFile Version 3.0\n" - "Data values for Tracks\n"; - if (write_ascii) { - VTKout << "ASCII\n"; - } else { - VTKout << "BINARY\n"; - } - VTKout << "DATASET POLYDATA\n" - "POINTS "; - // keep track of offset to write proper value later: - offset_num_points = VTKout.tellp(); - VTKout << "XXXXXXXXXX float\n"; + bool operator()(const Streamline &tck) { + // write out points, and build index of tracks: + size_t start_index = current_index; + current_index += tck.size(); + track_list.push_back(std::pair(start_index, current_index)); + if (write_ascii) { + for (const auto &pos : tck) { + VTKout << pos[0] << " " << pos[1] << " " << pos[2] << "\n"; } - - bool operator() (const Streamline& tck) { - // write out points, and build index of tracks: - size_t start_index = current_index; - current_index += tck.size(); - track_list.push_back (std::pair (start_index, current_index)); - if (write_ascii) { - for (const auto &pos : tck) { - VTKout << pos[0] << " " << pos[1] << " " << pos[2] << "\n"; - } - } else { - float p[3]; - for (const auto& pos : tck) { - for (auto i = 0; i < 3; ++i) Raw::store_BE(pos[i], p, i); - VTKout.write((char*)p, 3 * sizeof(float)); - } + } else { + float p[3]; + for (const auto &pos : tck) { + for (auto i = 0; i < 3; ++i) + Raw::store_BE(pos[i], p, i); + VTKout.write((char *)p, 3 * sizeof(float)); } - return true; } + return true; + } - ~VTKWriter() { - try { - // write out list of tracks: - if (write_ascii == false) { - // Need to include an extra new line when writing binary - VTKout << "\n"; - } - VTKout << "LINES " << track_list.size() << " " << track_list.size() + current_index << "\n"; - for (const auto& track : track_list) { - if (write_ascii) { - VTKout << track.second - track.first << " " << track.first; - for (size_t i = track.first + 1; i < track.second; ++i) - VTKout << " " << i; - VTKout << "\n"; - } - else { - int32_t buffer; - buffer = ByteOrder::BE (track.second - track.first); - VTKout.write ((char*) &buffer, 1 * sizeof(int32_t)); - - buffer = ByteOrder::BE (track.first); - VTKout.write ((char*) &buffer, 1 * sizeof(int32_t)); - - for (size_t i = track.first + 1; i < track.second; ++i) { - buffer = ByteOrder::BE (i); - VTKout.write ((char*)&buffer, 1* sizeof(int32_t)); - } - } - } - if (write_ascii == false) { - // Need to include an extra new line when writing binary + ~VTKWriter() { + try { + // write out list of tracks: + if (write_ascii == false) { + // Need to include an extra new line when writing binary + VTKout << "\n"; + } + VTKout << "LINES " << track_list.size() << " " << track_list.size() + current_index << "\n"; + for (const auto &track : track_list) { + if (write_ascii) { + VTKout << track.second - track.first << " " << track.first; + for (size_t i = track.first + 1; i < track.second; ++i) + VTKout << " " << i; VTKout << "\n"; - } + } else { + int32_t buffer; + buffer = ByteOrder::BE(track.second - track.first); + VTKout.write((char *)&buffer, 1 * sizeof(int32_t)); - // write back total number of points: - VTKout.seekp (offset_num_points); - std::string num_points (str (current_index)); - num_points.resize (10, ' '); - VTKout.write (num_points.c_str(), 10); + buffer = ByteOrder::BE(track.first); + VTKout.write((char *)&buffer, 1 * sizeof(int32_t)); - VTKout.close(); + for (size_t i = track.first + 1; i < track.second; ++i) { + buffer = ByteOrder::BE(i); + VTKout.write((char *)&buffer, 1 * sizeof(int32_t)); + } + } } - catch (Exception& e) { - e.display(); - App::exit_error_code = 1; + if (write_ascii == false) { + // Need to include an extra new line when writing binary + VTKout << "\n"; } - } - - private: - File::OFStream VTKout; - const bool write_ascii; - size_t offset_num_points; - vector> track_list; - size_t current_index = 0; - -}; - + // write back total number of points: + VTKout.seekp(offset_num_points); + std::string num_points(str(current_index)); + num_points.resize(10, ' '); + VTKout.write(num_points.c_str(), 10); + VTKout.close(); + } catch (Exception &e) { + e.display(); + App::exit_error_code = 1; + } + } +private: + File::OFStream VTKout; + const bool write_ascii; + size_t offset_num_points; + vector> track_list; + size_t current_index = 0; +}; -template void loadLines(vector& lines, std::ifstream& input, int number_of_line_indices) -{ - vector buffer (number_of_line_indices); - input.read((char*) &buffer[0], number_of_line_indices * sizeof(T)); - lines.resize (number_of_line_indices); +template void loadLines(vector &lines, std::ifstream &input, int number_of_line_indices) { + vector buffer(number_of_line_indices); + input.read((char *)&buffer[0], number_of_line_indices * sizeof(T)); + lines.resize(number_of_line_indices); // swap from big endian for (int i = 0; i < number_of_line_indices; i++) - lines[i] = int64_t (ByteOrder::BE (buffer[i])); + lines[i] = int64_t(ByteOrder::BE(buffer[i])); } +class VTKReader : public ReaderInterface { +public: + VTKReader(const std::string &file) { + std::ifstream input(file, std::ios::binary); + std::string line; + int number_of_points = 0; + number_of_lines = 0; + number_of_line_indices = 0; + while (std::getline(input, line)) { + if (line.find("ASCII") == 0) + throw Exception("VTK Reader only supports BINARY input"); -class VTKReader: public ReaderInterface { - public: - VTKReader (const std::string& file) { - std::ifstream input (file, std::ios::binary); - std::string line; - int number_of_points = 0; - number_of_lines = 0; - number_of_line_indices = 0; - - while (std::getline(input,line)) { - if (line.find ("ASCII") == 0) - throw Exception("VTK Reader only supports BINARY input"); + if (sscanf(line.c_str(), "POINTS %d float", &number_of_points) == 1) { + points.resize(3 * number_of_points); + input.read((char *)points.data(), 3 * number_of_points * sizeof(float)); - if (sscanf (line.c_str(), "POINTS %d float", &number_of_points) == 1) { - points.resize (3*number_of_points); - input.read ((char*) points.data(), 3*number_of_points * sizeof(float)); + // swap + for (int i = 0; i < 3 * number_of_points; i++) + points[i] = ByteOrder::BE(points[i]); - // swap - for (int i = 0; i < 3*number_of_points; i++) - points[i] = ByteOrder::BE (points[i]); - - continue; - } - else { - if (sscanf (line.c_str(), "LINES %d %d", &number_of_lines, &number_of_line_indices) == 2) { - if (line.find ("vtktypeint64") != std::string::npos) { - loadLines (lines, input, number_of_line_indices); - } else { - loadLines (lines, input, number_of_line_indices); - } - // We can safely break - break; + continue; + } else { + if (sscanf(line.c_str(), "LINES %d %d", &number_of_lines, &number_of_line_indices) == 2) { + if (line.find("vtktypeint64") != std::string::npos) { + loadLines(lines, input, number_of_line_indices); + } else { + loadLines(lines, input, number_of_line_indices); } + // We can safely break + break; } } - input.close(); - lineIdx = 0; } + input.close(); + lineIdx = 0; + } - bool operator() (Streamline& tck) { - tck.clear(); - if (lineIdx < number_of_line_indices) { - int count = lines[lineIdx]; + bool operator()(Streamline &tck) { + tck.clear(); + if (lineIdx < number_of_line_indices) { + int count = lines[lineIdx]; + lineIdx++; + for (int i = 0; i < count; i++) { + int idx = lines[lineIdx]; + Eigen::Vector3f f(points[idx * 3], points[idx * 3 + 1], points[idx * 3 + 2]); + tck.push_back(f); lineIdx++; - for ( int i = 0; i < count; i++ ) { - int idx = lines[lineIdx]; - Eigen::Vector3f f (points[idx*3], points[idx*3+1], points[idx*3+2]); - tck.push_back(f); - lineIdx++; - } - return true; } - return false; + return true; } + return false; + } - private: - vector points; - vector lines; - int lineIdx; - int number_of_lines; - int number_of_line_indices; - +private: + vector points; + vector lines; + int lineIdx; + int number_of_lines; + int number_of_line_indices; }; - - - - - -class ASCIIReader: public ReaderInterface { - public: - ASCIIReader(const std::string& file) { - auto num = list.parse_scan_check(file); - } - - bool operator() (Streamline& tck) { - tck.clear(); - if (item < list.size()) { - auto t = File::Matrix::load_matrix(list[item].name()); - for (size_t i = 0; i < size_t(t.rows()); i++) - tck.push_back(Eigen::Vector3f(t.row(i))); - item++; - return true; - } - return false; +class ASCIIReader : public ReaderInterface { +public: + ASCIIReader(const std::string &file) { auto num = list.parse_scan_check(file); } + + bool operator()(Streamline &tck) { + tck.clear(); + if (item < list.size()) { + auto t = File::Matrix::load_matrix(list[item].name()); + for (size_t i = 0; i < size_t(t.rows()); i++) + tck.push_back(Eigen::Vector3f(t.row(i))); + item++; + return true; } + return false; + } - ~ASCIIReader() { } - - private: - File::ParsedName::List list; - size_t item = 0; + ~ASCIIReader() {} +private: + File::ParsedName::List list; + size_t item = 0; }; +class ASCIIWriter : public WriterInterface { +public: + ASCIIWriter(const std::string &file) { + count.push_back(0); + parser.parse(file); + if (parser.ndim() != 1) + throw Exception("output file specifier should contain one placeholder for numbering (e.g. output-[].txt)"); + parser.calculate_padding({1000000}); + } + bool operator()(const Streamline &tck) { + std::string name = parser.name(count); + File::OFStream out(name); + for (auto i = tck.begin(); i != tck.end(); ++i) + out << (*i)[0] << " " << (*i)[1] << " " << (*i)[2] << "\n"; + out.close(); + count[0]++; + return true; + } + ~ASCIIWriter() {} - - - -class ASCIIWriter: public WriterInterface { - public: - ASCIIWriter(const std::string& file) { - count.push_back(0); - parser.parse(file); - if (parser.ndim() != 1) - throw Exception ("output file specifier should contain one placeholder for numbering (e.g. output-[].txt)"); - parser.calculate_padding({1000000}); - } - - bool operator() (const Streamline& tck) { - std::string name = parser.name(count); - File::OFStream out (name); - for (auto i = tck.begin(); i != tck.end(); ++i) - out << (*i) [0] << " " << (*i) [1] << " " << (*i) [2] << "\n"; - out.close(); - count[0]++; - return true; - } - - ~ASCIIWriter() { } - - private: - File::NameParser parser; - vector count; - +private: + File::NameParser parser; + vector count; }; +class PLYWriter : public WriterInterface { +public: + PLYWriter(const std::string &file, int increment = 1, float radius = 0.1, int sides = 5) + : out(file), increment(increment), radius(radius), sides(sides) { + vertexFilename = File::create_tempfile(0, "vertex"); + faceFilename = File::create_tempfile(0, "face"); + + vertexOF.open(vertexFilename); + faceOF.open(faceFilename); + num_faces = 0; + num_vertices = 0; + } + Eigen::Vector3f computeNormal(const Streamline &tck) { + // copy coordinates to matrix in Eigen format + size_t num_atoms = tck.size(); + Eigen::Matrix coord(3, num_atoms); + for (size_t i = 0; i < num_atoms; ++i) { + coord.col(i) = tck[i]; + } + // calculate centroid + Eigen::Vector3d centroid(coord.row(0).mean(), coord.row(1).mean(), coord.row(2).mean()); + // subtract centroid + coord.row(0).array() -= centroid(0); + coord.row(1).array() -= centroid(1); + coord.row(2).array() -= centroid(2); + // we only need the left-singular matrix here + // http://math.stackexchange.com/questions/99299/best-fitting-plane-given-a-set-of-points + auto svd = coord.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV); + Eigen::Vector3f plane_normal = svd.matrixU().rightCols<1>(); + return plane_normal; + } + void computeNormals(const Streamline &tck, Streamline &normals) { + Eigen::Vector3f sPrev = (tck[1] - tck[0]).normalized(); + Eigen::Vector3f normal = Eigen::Vector3f::Zero(); + + // Find a good starting normal + for (size_t idx = 1; idx < tck.size() - 1; idx++) { + const auto &pt1 = tck[idx]; + const auto &pt2 = tck[idx + 1]; + const auto sNext = (pt2 - pt1).normalized(); + const auto n = sPrev.cross(sNext); + if (n.norm() > 1.0E-3) { + normal = n; + sPrev = sNext; + break; + } + } -class PLYWriter: public WriterInterface { - public: - PLYWriter(const std::string& file, int increment = 1, float radius = 0.1, int sides = 5) : - out(file), increment(increment), - radius(radius), sides(sides) { - vertexFilename = File::create_tempfile (0,"vertex"); - faceFilename = File::create_tempfile (0,"face"); - - vertexOF.open(vertexFilename); - faceOF.open(faceFilename); - num_faces = 0; - num_vertices = 0; + normal.normalize(); // vtkPolyLine.cxx:170 + for (size_t idx = 0; idx < tck.size() - 1; idx++) { + const auto &pt1 = tck[idx]; + const auto &pt2 = tck[idx + 1]; + const auto sNext = (pt2 - pt1).normalized(); + // compute rotation vector vtkPolyLine.cxx:187 + auto w = sPrev.cross(normal); + if (w.norm() == 0.0) { + // copy the normal and continue + normals.push_back(normal); + continue; } - - Eigen::Vector3f computeNormal ( const Streamline& tck ) { - // copy coordinates to matrix in Eigen format - size_t num_atoms = tck.size(); - Eigen::Matrix< float, Eigen::Dynamic, Eigen::Dynamic > coord(3, num_atoms); - for (size_t i = 0; i < num_atoms; ++i) { - coord.col(i) = tck[i]; + // compute rotation of line segment + auto q = sNext.cross(sPrev); + if (q.norm() == 0.0) { + // copy the normal and continue + normals.push_back(normal); + continue; } - - // calculate centroid - Eigen::Vector3d centroid(coord.row(0).mean(), coord.row(1).mean(), coord.row(2).mean()); - - // subtract centroid - coord.row(0).array() -= centroid(0); - coord.row(1).array() -= centroid(1); - coord.row(2).array() -= centroid(2); - - // we only need the left-singular matrix here - // http://math.stackexchange.com/questions/99299/best-fitting-plane-given-a-set-of-points - auto svd = coord.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV); - Eigen::Vector3f plane_normal = svd.matrixU().rightCols<1>(); - return plane_normal; - } - - void computeNormals ( const Streamline& tck, Streamline& normals) { - Eigen::Vector3f sPrev = (tck[1] - tck[0]).normalized(); - Eigen::Vector3f normal = Eigen::Vector3f::Zero(); - - // Find a good starting normal - for (size_t idx = 1; idx < tck.size() - 1; idx++) { - const auto& pt1 = tck[idx]; - const auto& pt2 = tck[idx+1]; - const auto sNext = (pt2 - pt1).normalized(); - const auto n = sPrev.cross(sNext); - if ( n.norm() > 1.0E-3 ) { - normal = n; - sPrev = sNext; - break; - } + auto f1 = q.dot(normal); + auto f2 = 1.0 - (f1 * f1); + if (f2 > 0.0) { + f2 = sqrt(1.0 - (f1 * f1)); + } else { + f2 = 0.0; } - normal.normalize(); // vtkPolyLine.cxx:170 - for (size_t idx = 0; idx < tck.size() - 1; idx++) { - const auto& pt1 = tck[idx]; - const auto& pt2 = tck[idx+1]; - const auto sNext = (pt2 - pt1).normalized(); - - // compute rotation vector vtkPolyLine.cxx:187 - auto w = sPrev.cross(normal); - if ( w.norm() == 0.0 ) { - // copy the normal and continue - normals.push_back ( normal ); - continue; - } - // compute rotation of line segment - auto q = sNext.cross(sPrev); - if ( q.norm() == 0.0 ) { - // copy the normal and continue - normals.push_back ( normal ); - continue; - } - auto f1 = q.dot(normal); - auto f2 = 1.0 - ( f1 * f1 ); - if ( f2 > 0.0 ) { - f2 = sqrt(1.0 - (f1*f1)); - } else { - f2 = 0.0; - } - - auto c = (sNext + sPrev).normalized(); - w = c.cross(q); - c = sPrev.cross(q); - if ( ( normal.dot(c) * w.dot(c)) < 0 ) { - f2 = -1.0 * f2; - } - normals.push_back(normal); - sPrev = sNext; - normal = ( f1 * q ) + (f2 * w); + auto c = (sNext + sPrev).normalized(); + w = c.cross(q); + c = sPrev.cross(q); + if ((normal.dot(c) * w.dot(c)) < 0) { + f2 = -1.0 * f2; } + normals.push_back(normal); + sPrev = sNext; + normal = (f1 * q) + (f2 * w); } + } + bool operator()(const Streamline &intck) { + // Need at least 5 points, silently ignore... + if (intck.size() < size_t(increment * 3)) { + return true; + } - bool operator() (const Streamline& intck) { - // Need at least 5 points, silently ignore... - if (intck.size() < size_t(increment * 3)) { return true; } - - auto nSides = sides; - Eigen::MatrixXf coords(nSides,2); - Eigen::MatrixXi faces(nSides,6); - auto theta = 2.0 * Math::pi / float(nSides); - for ( auto i = 0; i < nSides; i++ ) { - coords(i,0) = cos((double)i*theta); - coords(i,1) = sin((double)i*theta); - // Face offsets - faces(i,0) = i; - faces(i,1) = (i+1) % nSides; - faces(i,2) = i+nSides; - faces(i,3) = (i+1) % nSides; - faces(i,4) = (i+1) % nSides + nSides; - faces(i,5) = i+nSides; - } + auto nSides = sides; + Eigen::MatrixXf coords(nSides, 2); + Eigen::MatrixXi faces(nSides, 6); + auto theta = 2.0 * Math::pi / float(nSides); + for (auto i = 0; i < nSides; i++) { + coords(i, 0) = cos((double)i * theta); + coords(i, 1) = sin((double)i * theta); + // Face offsets + faces(i, 0) = i; + faces(i, 1) = (i + 1) % nSides; + faces(i, 2) = i + nSides; + faces(i, 3) = (i + 1) % nSides; + faces(i, 4) = (i + 1) % nSides + nSides; + faces(i, 5) = i + nSides; + } - // to handle the increment, we want to keep the first 2 and last 2 points, but we can skip inside - Streamline tck; + // to handle the increment, we want to keep the first 2 and last 2 points, but we can skip inside + Streamline tck; - // Push on the first 2 points - tck.push_back(intck[0]); - tck.push_back(intck[1]); - for (size_t idx = 3; idx < intck.size() - 2; idx += increment) { - tck.push_back(intck[idx]); + // Push on the first 2 points + tck.push_back(intck[0]); + tck.push_back(intck[1]); + for (size_t idx = 3; idx < intck.size() - 2; idx += increment) { + tck.push_back(intck[idx]); + } + tck.push_back(intck[intck.size() - 2]); + tck.push_back(intck[intck.size() - 1]); + + Streamline normals; + this->computeNormals(tck, normals); + auto globalNormal = computeNormal(tck); + Eigen::Vector3f sNext = tck[1] - tck[0]; + auto isFirst = true; + for (size_t idx = 1; idx < tck.size() - 1; ++idx) { + auto isLast = idx == tck.size() - 2; + + // vtkTubeFilter.cxx:386 + Eigen::Vector3f p = tck[idx]; + Eigen::Vector3f pNext = tck[idx + 1]; + Eigen::Vector3f sPrev = sNext; + sNext = pNext - p; + Eigen::Vector3f n = normals[idx]; + + sNext.normalize(); + if (sNext.norm() == 0.0) { + continue; } - tck.push_back(intck[intck.size()-2]); - tck.push_back(intck[intck.size()-1]); - - Streamline normals; - this->computeNormals(tck,normals); - auto globalNormal = computeNormal(tck); - Eigen::Vector3f sNext = tck[1] - tck[0]; - auto isFirst = true; - for (size_t idx = 1; idx < tck.size() - 1; ++idx) { - auto isLast = idx == tck.size() - 2; - - // vtkTubeFilter.cxx:386 - Eigen::Vector3f p = tck[idx]; - Eigen::Vector3f pNext = tck[idx+1]; - Eigen::Vector3f sPrev = sNext; - sNext = pNext - p; - Eigen::Vector3f n = normals[idx]; - - sNext.normalize(); - if ( sNext.norm() == 0.0 ) { - continue; - } - // Average vectors - Eigen::Vector3f s = ( sPrev + sNext ) / 2.0; - s.normalize(); - if ( s.norm() == 0.0 ) { - s = sPrev.cross(n).normalized(); - } + // Average vectors + Eigen::Vector3f s = (sPrev + sNext) / 2.0; + s.normalize(); + if (s.norm() == 0.0) { + s = sPrev.cross(n).normalized(); + } - auto T = s; - auto N = T.cross(globalNormal).normalized(); - auto B = T.cross(N).normalized(); - N = B.cross(T).normalized(); - - - // have our coordinate frame, now add circles - for ( auto sideIdx = 0; sideIdx < nSides; sideIdx++ ) { - auto sidePoint = p + radius * ( N * coords(sideIdx,0) + B * coords(sideIdx,1)); - vertexOF << sidePoint[0] << " "<< sidePoint[1] << " " << sidePoint[2] << " "; - vertexOF << (int)( 255 * fabs(T[0])) << " " << (int)( 255 * fabs(T[1])) << " " << (int)( 255 * fabs(T[2])) << "\n"; - if ( !isLast ) { - faceOF << "3" - << " " << num_vertices + faces(sideIdx,0) - << " " << num_vertices + faces(sideIdx,1) - << " " << num_vertices + faces(sideIdx,2) << "\n"; - faceOF << "3" - << " " << num_vertices + faces(sideIdx,3) - << " " << num_vertices + faces(sideIdx,4) - << " " << num_vertices + faces(sideIdx,5) << "\n"; - num_faces += 2; - } + auto T = s; + auto N = T.cross(globalNormal).normalized(); + auto B = T.cross(N).normalized(); + N = B.cross(T).normalized(); + + // have our coordinate frame, now add circles + for (auto sideIdx = 0; sideIdx < nSides; sideIdx++) { + auto sidePoint = p + radius * (N * coords(sideIdx, 0) + B * coords(sideIdx, 1)); + vertexOF << sidePoint[0] << " " << sidePoint[1] << " " << sidePoint[2] << " "; + vertexOF << (int)(255 * fabs(T[0])) << " " << (int)(255 * fabs(T[1])) << " " << (int)(255 * fabs(T[2])) << "\n"; + if (!isLast) { + faceOF << "3" + << " " << num_vertices + faces(sideIdx, 0) << " " << num_vertices + faces(sideIdx, 1) << " " + << num_vertices + faces(sideIdx, 2) << "\n"; + faceOF << "3" + << " " << num_vertices + faces(sideIdx, 3) << " " << num_vertices + faces(sideIdx, 4) << " " + << num_vertices + faces(sideIdx, 5) << "\n"; + num_faces += 2; } - // Cap the first point, remebering the right hand rule - if ( isFirst ) { - for ( auto sideIdx = nSides - 1; sideIdx >= 2; --sideIdx ) { - faceOF << "3" - << " " << num_vertices + sideIdx - << " " << num_vertices + sideIdx - 1 - << " " << num_vertices << "\n"; - } - num_faces += nSides - 2; - isFirst = false; + } + // Cap the first point, remebering the right hand rule + if (isFirst) { + for (auto sideIdx = nSides - 1; sideIdx >= 2; --sideIdx) { + faceOF << "3" + << " " << num_vertices + sideIdx << " " << num_vertices + sideIdx - 1 << " " << num_vertices << "\n"; } - if ( isLast ) { - for ( auto sideIdx = 2; sideIdx <= nSides - 1; ++sideIdx ) { - faceOF << "3" - << " " << num_vertices + sideIdx - 1 - << " " << num_vertices + sideIdx - << " " << num_vertices - << "\n"; - } - num_faces += nSides - 2; + num_faces += nSides - 2; + isFirst = false; + } + if (isLast) { + for (auto sideIdx = 2; sideIdx <= nSides - 1; ++sideIdx) { + faceOF << "3" + << " " << num_vertices + sideIdx - 1 << " " << num_vertices + sideIdx << " " << num_vertices << "\n"; } - // We needed to maintain the number of vertices for the caps, now increment for the "circles" - num_vertices += nSides; + num_faces += nSides - 2; } - return true; + // We needed to maintain the number of vertices for the caps, now increment for the "circles" + num_vertices += nSides; } + return true; + } - ~PLYWriter() { - try { - // write out list of tracks: - vertexOF.close(); - faceOF.close(); - - out << - "ply\n" - "format ascii 1.0\n" - "comment written by tckconvert v" << App::mrtrix_version << "\n" - "comment part of the mtrix3 suite of tools (http://www.mrtrix.org/)\n" - "comment the coordinate system and scale is taken from directly from the input and is not adjusted\n" - "element vertex " << num_vertices << "\n" - "property float32 x\n" - "property float32 y\n" - "property float32 z\n" - "property uchar red\n" - "property uchar green\n" - "property uchar blue\n" - "element face " << num_faces << "\n" - "property list uint8 int32 vertex_indices\n" - "end_header\n"; - - std::ifstream vertexIF (vertexFilename); - out << vertexIF.rdbuf(); - vertexIF.close(); - File::remove (vertexFilename); - - std::ifstream faceIF (faceFilename); - out << faceIF.rdbuf(); - faceIF.close(); - File::remove (faceFilename); - - out.close(); - } catch (Exception& e) { - e.display(); - App::exit_error_code = 1; - } + ~PLYWriter() { + try { + // write out list of tracks: + vertexOF.close(); + faceOF.close(); + + out << "ply\n" + "format ascii 1.0\n" + "comment written by tckconvert v" + << App::mrtrix_version + << "\n" + "comment part of the mtrix3 suite of tools (http://www.mrtrix.org/)\n" + "comment the coordinate system and scale is taken from directly from the input and is not adjusted\n" + "element vertex " + << num_vertices + << "\n" + "property float32 x\n" + "property float32 y\n" + "property float32 z\n" + "property uchar red\n" + "property uchar green\n" + "property uchar blue\n" + "element face " + << num_faces + << "\n" + "property list uint8 int32 vertex_indices\n" + "end_header\n"; + + std::ifstream vertexIF(vertexFilename); + out << vertexIF.rdbuf(); + vertexIF.close(); + File::remove(vertexFilename); + + std::ifstream faceIF(faceFilename); + out << faceIF.rdbuf(); + faceIF.close(); + File::remove(faceFilename); + + out.close(); + } catch (Exception &e) { + e.display(); + App::exit_error_code = 1; } + } - private: - std::string vertexFilename; - std::string faceFilename; - File::OFStream out; - File::OFStream vertexOF; - File::OFStream faceOF; - size_t num_vertices; - size_t num_faces; - int increment; - float radius; - int sides; +private: + std::string vertexFilename; + std::string faceFilename; + File::OFStream out; + File::OFStream vertexOF; + File::OFStream faceOF; + size_t num_vertices; + size_t num_faces; + int increment; + float radius; + int sides; }; +class RibWriter : public WriterInterface { +public: + RibWriter(const std::string &file, float radius = 0.1, bool dec = false) + : out(file), writeDEC(dec), radius(radius), hasPoints(false), wroteHeader(false) { + pointsFilename = File::create_tempfile(0, "points"); + pointsOF.open(pointsFilename); + pointsOF << "\"P\" ["; + decFilename = File::create_tempfile(0, "dec"); + decOF.open(decFilename); + decOF << "\"varying color dec\" ["; + // Header + out << "##RenderMan RIB\n" + << "# Written by tckconvert\n" + << "# Part of the MRtrix package (http://mrtrix.org)\n" + << "# version: " << App::mrtrix_version << "\n"; + } + bool operator()(const Streamline &tck) { + if (tck.size() < 3) { + return true; + } - - - - - - - -class RibWriter: public WriterInterface { - public: - RibWriter(const std::string& file, float radius = 0.1, bool dec = false) : - out(file), writeDEC(dec), radius(radius), - hasPoints(false), wroteHeader(false) { - pointsFilename = File::create_tempfile (0,"points"); - pointsOF.open (pointsFilename); - pointsOF << "\"P\" ["; - decFilename = File::create_tempfile (0,"dec"); - decOF.open (decFilename); - decOF << "\"varying color dec\" ["; - // Header - out << "##RenderMan RIB\n" - << "# Written by tckconvert\n" - << "# Part of the MRtrix package (http://mrtrix.org)\n" - << "# version: " << App::mrtrix_version << "\n"; - - } - - bool operator() (const Streamline& tck) { - if ( tck.size() < 3 ) { - return true; - } - - hasPoints = true; - if ( !wroteHeader ) { - wroteHeader = true; - // Start writing the header - out << "Basis \"catmull-rom\" 1 \"catmull-rom\" 1\n" + hasPoints = true; + if (!wroteHeader) { + wroteHeader = true; + // Start writing the header + out << "Basis \"catmull-rom\" 1 \"catmull-rom\" 1\n" << "Attribute \"dice\" \"int roundcurve\" [1] \"int hair\" [1]\n" << "Curves \"linear\" ["; + } + out << tck.size() << " "; + Eigen::Vector3f prev = tck[1]; + for (auto pt : tck) { + pointsOF << pt[0] << " " << pt[1] << " " << pt[2] << " "; + // Should we write the dec? + if (writeDEC) { + Eigen::Vector3f T = (prev - pt).normalized(); + decOF << fabs(T[0]) << " " << fabs(T[1]) << " " << fabs(T[2]) << " "; + prev = pt; } - out << tck.size() << " "; - Eigen::Vector3f prev = tck[1]; - for ( auto pt : tck ) { - pointsOF << pt[0] << " " << pt[1] << " " << pt[2] << " "; - // Should we write the dec? - if ( writeDEC ) { - Eigen::Vector3f T = ( prev - pt ).normalized(); - decOF << fabs(T[0]) << " " << fabs(T[1]) << " " << fabs(T[2]) << " "; - prev = pt; - } - } - return true; } + return true; + } - ~RibWriter() { - try { - - if (hasPoints) { - pointsOF << "]\n" ; - decOF << "]\n" ; - } + ~RibWriter() { + try { - pointsOF.close(); - decOF.close(); + if (hasPoints) { + pointsOF << "]\n"; + decOF << "]\n"; + } - if (hasPoints) { - out << "] \"nonperiodic\" "; + pointsOF.close(); + decOF.close(); - std::ifstream pointsIF ( pointsFilename ); - out << pointsIF.rdbuf(); + if (hasPoints) { + out << "] \"nonperiodic\" "; - if ( writeDEC ) { - std::ifstream decIF ( decFilename ); - out << decIF.rdbuf(); - decIF.close(); - } + std::ifstream pointsIF(pointsFilename); + out << pointsIF.rdbuf(); - out << " \"constantwidth\" " << radius << "\n"; + if (writeDEC) { + std::ifstream decIF(decFilename); + out << decIF.rdbuf(); + decIF.close(); } - out.close(); - - File::remove (pointsFilename); - File::remove (decFilename); - - } catch (Exception& e) { - e.display(); - App::exit_error_code = 1; + out << " \"constantwidth\" " << radius << "\n"; } - } - - - private: - std::string pointsFilename; - std::string decFilename; - File::OFStream out; - File::OFStream pointsOF; - File::OFStream decOF; - bool writeDEC; - float radius; - bool hasPoints; - bool wroteHeader; -}; - - - - - - - + out.close(); + File::remove(pointsFilename); + File::remove(decFilename); + } catch (Exception &e) { + e.display(); + App::exit_error_code = 1; + } + } +private: + std::string pointsFilename; + std::string decFilename; + File::OFStream out; + File::OFStream pointsOF; + File::OFStream decOF; + bool writeDEC; + float radius; + bool hasPoints; + bool wroteHeader; +}; -void run () -{ +void run() { // Reader Properties properties; - std::unique_ptr > reader; + std::unique_ptr> reader; if (Path::has_suffix(argument[0], ".tck")) { - reader.reset (new Reader(argument[0], properties)); - } - else if (Path::has_suffix(argument[0], ".txt")) { - reader.reset (new ASCIIReader(argument[0])); - } - else if (Path::has_suffix(argument[0], ".vtk")) { - reader.reset (new VTKReader(argument[0])); + reader.reset(new Reader(argument[0], properties)); + } else if (Path::has_suffix(argument[0], ".txt")) { + reader.reset(new ASCIIReader(argument[0])); + } else if (Path::has_suffix(argument[0], ".vtk")) { + reader.reset(new VTKReader(argument[0])); + } else { + throw Exception("Unsupported input file type."); } - else { - throw Exception ("Unsupported input file type."); - } - // Writer - std::unique_ptr > writer; + std::unique_ptr> writer; if (Path::has_suffix(argument[1], ".tck")) { - writer.reset (new Writer(argument[1], properties)); - } - else if (Path::has_suffix(argument[1], ".vtk")) { + writer.reset(new Writer(argument[1], properties)); + } else if (Path::has_suffix(argument[1], ".vtk")) { auto write_ascii = get_options("ascii").size(); - writer.reset (new VTKWriter(argument[1], write_ascii)); - } - else if (Path::has_suffix(argument[1], ".ply")) { + writer.reset(new VTKWriter(argument[1], write_ascii)); + } else if (Path::has_suffix(argument[1], ".ply")) { auto increment = get_options("increment").size() ? get_options("increment")[0][0].as_int() : 1; auto radius = get_options("radius").size() ? get_options("radius")[0][0].as_float() : 0.1f; auto sides = get_options("sides").size() ? get_options("sides")[0][0].as_int() : 5; - writer.reset (new PLYWriter(argument[1], increment, radius, sides)); - } - else if (Path::has_suffix(argument[1], ".rib")) { - writer.reset (new RibWriter(argument[1])); - } - else if (Path::has_suffix(argument[1], ".txt")) { - writer.reset (new ASCIIWriter(argument[1])); + writer.reset(new PLYWriter(argument[1], increment, radius, sides)); + } else if (Path::has_suffix(argument[1], ".rib")) { + writer.reset(new RibWriter(argument[1])); + } else if (Path::has_suffix(argument[1], ".txt")) { + writer.reset(new ASCIIWriter(argument[1])); + } else { + throw Exception("Unsupported output file type."); } - else { - throw Exception ("Unsupported output file type."); - } - // Tranform matrix transform_type T; @@ -805,15 +724,12 @@ void run () throw Exception("Transform options are mutually exclusive."); } - // Copy Streamline tck; while ((*reader)(tck)) { - for (auto& pos : tck) { + for (auto &pos : tck) { pos = T.cast() * pos; } (*writer)(tck); } - } - diff --git a/cmd/tckdfc.cpp b/cmd/tckdfc.cpp index 9ce52b2843..45753be78e 100644 --- a/cmd/tckdfc.cpp +++ b/cmd/tckdfc.cpp @@ -32,13 +32,8 @@ #include "dwi/tractography/mapping/voxel.h" #include "dwi/tractography/mapping/writer.h" - - #define MAX_VOXEL_STEP_RATIO 0.333 - - - using namespace MR; using namespace App; @@ -46,158 +41,147 @@ using namespace MR::DWI; using namespace MR::DWI::Tractography; using namespace MR::DWI::Tractography::Mapping; +const char *windows[] = {"rectangle", "triangle", "cosine", "hann", "hamming", "lanczos", nullptr}; +void usage() { -const char* windows[] = { "rectangle", "triangle", "cosine", "hann", "hamming", "lanczos", nullptr }; - - -void usage () { - -AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; - -SYNOPSIS = "Perform the Track-Weighted Dynamic Functional Connectivity (TW-dFC) method"; - -DESCRIPTION - + "This command generates a Track-Weighted Image (TWI), where the " - "contribution from each streamline to the image is the Pearson " - "correlation between the fMRI time series at the streamline endpoints." - - + "The output image can be generated in one of two ways " - "(note that one of these two command-line options MUST be provided): " + AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; - + "- \"Static\" functional connectivity (-static option): " - "Each streamline contributes to a static 3D output image based on the " - "correlation between the signals at the streamline endpoints using the " - "entirety of the input time series." + SYNOPSIS = "Perform the Track-Weighted Dynamic Functional Connectivity (TW-dFC) method"; - + "- \"Dynamic\" functional connectivity (-dynamic option): " - "The output image is a 4D image, with the same number of volumes as " - "the input fMRI time series. For each volume, the contribution from " - "each streamline is calculated based on a finite-width sliding time " - "window, centred at the timepoint corresponding to that volume." + DESCRIPTION + +"This command generates a Track-Weighted Image (TWI), where the " + "contribution from each streamline to the image is the Pearson " + "correlation between the fMRI time series at the streamline endpoints." - + "Note that the -backtrack option in this command is similar, but not precisely " - "equivalent, to back-tracking as can be used with Anatomically-Constrained " - "Tractography (ACT) in the tckgen command. However, here the feature does not " - "change the streamlines trajectories in any way; it simply enables detection of " - "the fact that the input fMRI image may not contain a valid timeseries underneath " - "the streamline endpoint, and where this occurs, searches from the streamline " - "endpoint inwards along the streamline trajectory in search of a valid " - "timeseries to sample from the input image."; + + "The output image can be generated in one of two ways " + "(note that one of these two command-line options MUST be provided): " -ARGUMENTS - + Argument ("tracks", "the input track file.").type_file_in() - + Argument ("fmri", "the pre-processed fMRI time series").type_image_in() - + Argument ("output", "the output TW-dFC image").type_image_out(); + + "- \"Static\" functional connectivity (-static option): " + "Each streamline contributes to a static 3D output image based on the " + "correlation between the signals at the streamline endpoints using the " + "entirety of the input time series." -OPTIONS - + OptionGroup ("Options for toggling between static and dynamic TW-dFC methods; " - "note that one of these options MUST be provided") + + "- \"Dynamic\" functional connectivity (-dynamic option): " + "The output image is a 4D image, with the same number of volumes as " + "the input fMRI time series. For each volume, the contribution from " + "each streamline is calculated based on a finite-width sliding time " + "window, centred at the timepoint corresponding to that volume." - + Option ("static", "generate a \"static\" (3D) output image.") + + "Note that the -backtrack option in this command is similar, but not precisely " + "equivalent, to back-tracking as can be used with Anatomically-Constrained " + "Tractography (ACT) in the tckgen command. However, here the feature does not " + "change the streamlines trajectories in any way; it simply enables detection of " + "the fact that the input fMRI image may not contain a valid timeseries underneath " + "the streamline endpoint, and where this occurs, searches from the streamline " + "endpoint inwards along the streamline trajectory in search of a valid " + "timeseries to sample from the input image."; - + Option ("dynamic", "generate a \"dynamic\" (4D) output image; " - "must additionally provide the shape and width (in volumes) of the sliding window.") - + Argument ("shape").type_choice (windows) - + Argument ("width").type_integer (3) + ARGUMENTS + +Argument("tracks", "the input track file.").type_file_in() + + Argument("fmri", "the pre-processed fMRI time series").type_image_in() + + Argument("output", "the output TW-dFC image").type_image_out(); + OPTIONS + +OptionGroup("Options for toggling between static and dynamic TW-dFC methods; " + "note that one of these options MUST be provided") - + OptionGroup ("Options for setting the properties of the output image") + + Option("static", "generate a \"static\" (3D) 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("dynamic", + "generate a \"dynamic\" (4D) output image; " + "must additionally provide the shape and width (in volumes) of the sliding window.") + + Argument("shape").type_choice(windows) + Argument("width").type_integer(3) - + Option ("vox", - "provide either an isotropic voxel size (in mm), or comma-separated list " - "of 3 voxel dimensions.") - + Argument ("size").type_sequence_float() + + OptionGroup("Options for setting the properties of the output image") - + 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: mean)") - + Argument ("type").type_choice (voxel_statistics) + + 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() - + OptionGroup ("Other options for affecting the streamline sampling & mapping behaviour") + + 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: mean)") + + Argument("type").type_choice(voxel_statistics) - + Option ("backtrack", - "if no valid timeseries is found at the streamline endpoint, back-track along " - "the streamline trajectory until a valid timeseries is found") + + OptionGroup("Other options for affecting the streamline sampling & mapping behaviour") - + Option ("upsample", - "upsample the tracks by some ratio using Hermite interpolation before mapping " - "(if omitted, an appropriate ratio will be determined automatically)") - + Argument ("factor").type_integer (1); + + Option("backtrack", + "if no valid timeseries is found at the streamline endpoint, back-track along " + "the streamline trajectory until a valid timeseries is found") + + Option("upsample", + "upsample the tracks by some ratio using Hermite interpolation before mapping " + "(if omitted, an appropriate ratio will be determined automatically)") + + Argument("factor").type_integer(1); REFERENCES - + "Calamante, F.; Smith, R.E.; Liang, X.; Zalesky, A.; Connelly, A " // Internal - "Track-weighted dynamic functional connectivity (TW-dFC): a new method to study time-resolved functional connectivity. " - "Brain Struct Funct, 2017, doi: 10.1007/s00429-017-1431-1"; - + +"Calamante, F.; Smith, R.E.; Liang, X.; Zalesky, A.; Connelly, A " // Internal + "Track-weighted dynamic functional connectivity (TW-dFC): a new method to study time-resolved functional " + "connectivity. " + "Brain Struct Funct, 2017, doi: 10.1007/s00429-017-1431-1"; } - - - - // This class is similar to Mapping::MapWriter, but doesn't write to a HDD file on close // Instead, the one timepoint volume generated during this iteration is written // into the one large buffer that contains the entire TW-dFC time series -class Receiver -{ - - public: - Receiver (const Header& header, const vox_stat_t stat_vox) : - buffer (Image::scratch (header, "TW-dFC scratch buffer")), - vox_stat (stat_vox) - { - if (vox_stat == V_MIN) { - for (auto l = Loop(buffer) (buffer); l; ++l) - buffer.value() = std::numeric_limits::infinity(); - } else if (vox_stat == V_MAX) { - for (auto l = Loop(buffer) (buffer); l; ++l) - buffer.value() = -std::numeric_limits::infinity(); - } +class Receiver { + +public: + Receiver(const Header &header, const vox_stat_t stat_vox) + : buffer(Image::scratch(header, "TW-dFC scratch buffer")), vox_stat(stat_vox) { + if (vox_stat == V_MIN) { + for (auto l = Loop(buffer)(buffer); l; ++l) + buffer.value() = std::numeric_limits::infinity(); + } else if (vox_stat == V_MAX) { + for (auto l = Loop(buffer)(buffer); l; ++l) + buffer.value() = -std::numeric_limits::infinity(); } + } + bool operator()(const Mapping::SetVoxel &); + void scale_by_count(Image &); + void write(Image &); - bool operator() (const Mapping::SetVoxel&); - void scale_by_count (Image&); - void write (Image&); - - - private: - Image buffer; - const vox_stat_t vox_stat; - +private: + Image buffer; + const vox_stat_t vox_stat; }; - - -bool Receiver::operator() (const Mapping::SetVoxel& in) -{ +bool Receiver::operator()(const Mapping::SetVoxel &in) { const float factor = in.factor; - for (const auto& i : in) { - assign_pos_of (i, 0, 3).to (buffer); + for (const auto &i : in) { + assign_pos_of(i, 0, 3).to(buffer); switch (vox_stat) { - case V_SUM: buffer.value() += factor; break; - case V_MIN: buffer.value() = std::min (float(buffer.value()), factor); break; - case V_MAX: buffer.value() = std::max (float(buffer.value()), factor); break; - case V_MEAN: buffer.value() += factor; break; + case V_SUM: + buffer.value() += factor; + break; + case V_MIN: + buffer.value() = std::min(float(buffer.value()), factor); + break; + case V_MAX: + buffer.value() = std::max(float(buffer.value()), factor); + break; + case V_MEAN: + buffer.value() += factor; + break; // Unlike Mapping::MapWriter, don't need to deal with counts here } } return true; } -void Receiver::scale_by_count (Image& counts) -{ - assert (dimensions_match (buffer, counts, 0, 3)); - for (auto l = Loop(buffer) (buffer, counts); l; ++l) { +void Receiver::scale_by_count(Image &counts) { + assert(dimensions_match(buffer, counts, 0, 3)); + for (auto l = Loop(buffer)(buffer, counts); l; ++l) { if (counts.value()) buffer.value() /= float(counts.value()); else @@ -205,98 +189,85 @@ void Receiver::scale_by_count (Image& counts) } } -void Receiver::write (Image& out) -{ - for (auto l = Loop(buffer) (buffer, out); l; ++l) +void Receiver::write(Image &out) { + for (auto l = Loop(buffer)(buffer, out); l; ++l) out.value() = buffer.value(); } - - - - - - // Separate class for generating TDI i.e. receive SetVoxel & write directly to counts -class Count_receiver -{ - public: - Count_receiver (Image& out) : - v (out) { } - bool operator() (const Mapping::SetVoxel& in) { - for (const auto& i : in) { - assign_pos_of (i, 0, 3).to (v); - v.value() = v.value() + 1; - } - return true; +class Count_receiver { +public: + Count_receiver(Image &out) : v(out) {} + bool operator()(const Mapping::SetVoxel &in) { + for (const auto &i : in) { + assign_pos_of(i, 0, 3).to(v); + v.value() = v.value() + 1; } - private: - Image v; -}; - - - + return true; + } +private: + Image v; +}; -void run () -{ - bool is_static = get_options ("static").size(); +void run() { + bool is_static = get_options("static").size(); vector window; - auto opt = get_options ("dynamic"); + auto opt = get_options("dynamic"); if (opt.size()) { if (is_static) - throw Exception ("Do not specify both -static and -dynamic options"); + throw Exception("Do not specify both -static and -dynamic options"); // Generate the window filter const int window_shape = opt[0][0]; const ssize_t window_width = opt[0][1]; if (!(window_width % 2)) - throw Exception ("Width of sliding time window must be an odd integer"); + throw Exception("Width of sliding time window must be an odd integer"); - window.resize (window_width); - const ssize_t halfwidth = (window_width+1) / 2; - const ssize_t centre = (window_width-1) / 2; // Element at centre of the window + window.resize(window_width); + const ssize_t halfwidth = (window_width + 1) / 2; + const ssize_t centre = (window_width - 1) / 2; // Element at centre of the window switch (window_shape) { - case 0: // rectangular - window.assign (window_width, 1.0); - break; - - case 1: // triangle - for (ssize_t i = 0; i != window_width; ++i) - window[i] = 1.0 - (abs (i - centre) / default_type(halfwidth)); - break; - - case 2: // cosine - for (ssize_t i = 0; i != window_width; ++i) - window[i] = std::sin (i * Math::pi / default_type(window_width - 1)); - break; - - case 3: // hann - for (ssize_t i = 0; i != window_width; ++i) - window[i] = 0.5 * (1.0 - std::cos (2.0 * Math::pi * i / default_type(window_width - 1))); - break; - - case 4: // hamming - for (ssize_t i = 0; i != window_width; ++i) - window[i] = 0.53836 - (0.46164 * std::cos (2.0 * Math::pi * i / default_type(window_width - 1))); - break; - - case 5: // lanczos - for (ssize_t i = 0; i != window_width; ++i) { - const default_type v = 2.0 * Math::pi * abs (i - centre) / default_type(window_width - 1); - window[i] = v ? std::max (0.0, (std::sin (v) / v)) : 1.0; - } - break; - - default: - throw Exception ("Unsupported sliding window shape"); + case 0: // rectangular + window.assign(window_width, 1.0); + break; + + case 1: // triangle + for (ssize_t i = 0; i != window_width; ++i) + window[i] = 1.0 - (abs(i - centre) / default_type(halfwidth)); + break; + + case 2: // cosine + for (ssize_t i = 0; i != window_width; ++i) + window[i] = std::sin(i * Math::pi / default_type(window_width - 1)); + break; + + case 3: // hann + for (ssize_t i = 0; i != window_width; ++i) + window[i] = 0.5 * (1.0 - std::cos(2.0 * Math::pi * i / default_type(window_width - 1))); + break; + + case 4: // hamming + for (ssize_t i = 0; i != window_width; ++i) + window[i] = 0.53836 - (0.46164 * std::cos(2.0 * Math::pi * i / default_type(window_width - 1))); + break; + + case 5: // lanczos + for (ssize_t i = 0; i != window_width; ++i) { + const default_type v = 2.0 * Math::pi * abs(i - centre) / default_type(window_width - 1); + window[i] = v ? std::max(0.0, (std::sin(v) / v)) : 1.0; + } + break; + + default: + throw Exception("Unsupported sliding window shape"); } } else if (!is_static) { - throw Exception ("Either the -static or -dynamic option must be provided"); + throw Exception("Either the -static or -dynamic option must be provided"); } const std::string tck_path = argument[0]; @@ -304,35 +275,38 @@ void run () { // Just get the properties for now; will re-instantiate the reader multiple times later // TODO Constructor for properties using the file path? - Tractography::Reader tck_file (tck_path, properties); + Tractography::Reader tck_file(tck_path, properties); } - const size_t num_tracks = properties["count"].empty() ? 0 : to (properties["count"]); + const size_t num_tracks = properties["count"].empty() ? 0 : to(properties["count"]); - Image fmri_image (Image::open (argument[1]).with_direct_io(3)); + Image fmri_image(Image::open(argument[1]).with_direct_io(3)); vector voxel_size; opt = get_options("vox"); if (opt.size()) - voxel_size = parse_floats (opt[0][0]); + voxel_size = parse_floats(opt[0][0]); if (voxel_size.size() == 1) - voxel_size.assign (3, voxel_size.front()); + voxel_size.assign(3, voxel_size.front()); 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"); + 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]) + " ]"); + INFO("creating image with voxel dimensions [ " + str(voxel_size[0]) + " " + str(voxel_size[1]) + " " + + str(voxel_size[2]) + " ]"); Header header; - opt = get_options ("template"); + opt = get_options("template"); if (opt.size()) { - header = Header::open (opt[0][0]); + header = Header::open(opt[0][0]); if (!voxel_size.empty()) - Mapping::oversample_header (header, voxel_size); + Mapping::oversample_header(header, voxel_size); } else { if (voxel_size.empty()) - throw Exception ("please specify either a template image using the -template option, or the desired voxel size using the -vox option"); - Mapping::generate_header (header, argument[0], voxel_size); + throw Exception("please specify either a template image using the -template option, or the desired voxel size " + "using the -vox option"); + Mapping::generate_header(header, argument[0], voxel_size); } header.datatype() = DataType::Float32; @@ -343,79 +317,88 @@ void run () header.ndim() = 4; header.size(3) = fmri_image.size(3); } - add_line (header.keyval()["comments"], "TW-dFC image"); + add_line(header.keyval()["comments"], "TW-dFC image"); size_t upsample_ratio; - opt = get_options ("upsample"); + opt = get_options("upsample"); if (opt.size()) { upsample_ratio = opt[0][0]; - INFO ("track interpolation factor manually set to " + str(upsample_ratio)); - } else { + INFO("track interpolation factor manually set to " + str(upsample_ratio)); + } else { try { - upsample_ratio = determine_upsample_ratio (header, properties, MAX_VOXEL_STEP_RATIO); - INFO ("track interpolation factor 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)"); + upsample_ratio = determine_upsample_ratio(header, properties, MAX_VOXEL_STEP_RATIO); + INFO("track interpolation factor 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; } } - opt = get_options ("stat_vox"); + opt = get_options("stat_vox"); const vox_stat_t stat_vox = opt.size() ? vox_stat_t(int(opt[0][0])) : V_MEAN; - Header H_3D (header); + Header H_3D(header); H_3D.ndim() = 3; if (is_static) { - Tractography::Reader tck_file (tck_path, properties); - Mapping::TrackLoader loader (tck_file, num_tracks, "Generating (static) TW-dFC image"); - Mapping::TrackMapperTWI mapper (H_3D, SCALAR_MAP, ENDS_CORR); - mapper.set_upsample_ratio (upsample_ratio); - mapper.add_twdfc_static_image (fmri_image); - Mapping::MapWriter writer (header, argument[2], stat_vox); - Thread::run_queue (loader, Thread::batch (Tractography::Streamline<>()), Thread::multi (mapper), Thread::batch (Mapping::SetVoxel()), writer); + Tractography::Reader tck_file(tck_path, properties); + Mapping::TrackLoader loader(tck_file, num_tracks, "Generating (static) TW-dFC image"); + Mapping::TrackMapperTWI mapper(H_3D, SCALAR_MAP, ENDS_CORR); + mapper.set_upsample_ratio(upsample_ratio); + mapper.add_twdfc_static_image(fmri_image); + Mapping::MapWriter writer(header, argument[2], stat_vox); + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline<>()), + Thread::multi(mapper), + Thread::batch(Mapping::SetVoxel()), + writer); writer.finalise(); } else { Image counts; if (stat_vox == V_MEAN) { - counts = Image::scratch (H_3D, "Track count scratch buffer"); - Tractography::Reader tck_file (tck_path, properties); - Mapping::TrackLoader loader (tck_file, num_tracks, "Calculating initial TDI"); - Mapping::TrackMapperBase mapper (H_3D); - mapper.set_upsample_ratio (upsample_ratio); - Count_receiver receiver (counts); - Thread::run_queue (loader, Thread::batch (Tractography::Streamline<>()), Thread::multi (mapper), Thread::batch (Mapping::SetVoxel()), receiver); + counts = Image::scratch(H_3D, "Track count scratch buffer"); + Tractography::Reader tck_file(tck_path, properties); + Mapping::TrackLoader loader(tck_file, num_tracks, "Calculating initial TDI"); + Mapping::TrackMapperBase mapper(H_3D); + mapper.set_upsample_ratio(upsample_ratio); + Count_receiver receiver(counts); + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline<>()), + Thread::multi(mapper), + Thread::batch(Mapping::SetVoxel()), + receiver); } - Image out_image (Image::create (argument[2], header)); - ProgressBar progress ("Generating TW-dFC image", header.size(3)); + Image out_image(Image::create(argument[2], header)); + ProgressBar progress("Generating TW-dFC image", header.size(3)); for (ssize_t timepoint = 0; timepoint != header.size(3); ++timepoint) { { - LogLevelLatch latch (0); - Tractography::Reader tck_file (tck_path, properties); - Mapping::TrackLoader loader (tck_file); - Mapping::TrackMapperTWI mapper (H_3D, SCALAR_MAP, ENDS_CORR); - mapper.set_upsample_ratio (upsample_ratio); - mapper.add_twdfc_dynamic_image (fmri_image, window, timepoint); - Receiver receiver (H_3D, stat_vox); - Thread::run_queue (loader, Thread::batch (Tractography::Streamline<>()), Thread::multi (mapper), Thread::batch (Mapping::SetVoxel()), receiver); + LogLevelLatch latch(0); + Tractography::Reader tck_file(tck_path, properties); + Mapping::TrackLoader loader(tck_file); + Mapping::TrackMapperTWI mapper(H_3D, SCALAR_MAP, ENDS_CORR); + mapper.set_upsample_ratio(upsample_ratio); + mapper.add_twdfc_dynamic_image(fmri_image, window, timepoint); + Receiver receiver(H_3D, stat_vox); + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline<>()), + Thread::multi(mapper), + Thread::batch(Mapping::SetVoxel()), + receiver); if (stat_vox == V_MEAN) - receiver.scale_by_count (counts); + receiver.scale_by_count(counts); out_image.index(3) = timepoint; - receiver.write (out_image); + receiver.write(out_image); } ++progress; } - } } - - diff --git a/cmd/tckedit.cpp b/cmd/tckedit.cpp index b482c2c5e2..afc3963636 100644 --- a/cmd/tckedit.cpp +++ b/cmd/tckedit.cpp @@ -32,113 +32,96 @@ #include "dwi/tractography/editing/receiver.h" #include "dwi/tractography/editing/worker.h" - - using namespace MR; using namespace App; using namespace MR::DWI; using namespace MR::DWI::Tractography; using namespace MR::DWI::Tractography::Editing; - - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Perform various editing operations on track files"; DESCRIPTION - + "This command can be used to perform various types of manipulations " - "on track data. A range of such manipulations are demonstrated in the " - "examples provided below."; + +"This command can be used to perform various types of manipulations " + "on track data. A range of such manipulations are demonstrated in the " + "examples provided below."; EXAMPLES - + Example ("Concatenate data from multiple track files into one", - "tckedit *.tck all_tracks.tck", - "Here the wildcard operator is used to select all files in the " - "current working directory that have the .tck filetype suffix; but " - "input files can equivalently be specified one at a time explicitly.") - - + Example ("Extract a reduced number of streamlines", - "tckedit in_many.tck out_few.tck -number 1k -skip 500", - "The number of streamlines requested would typically be less " - "than the number of streamlines in the input track file(s); if it " - "is instead greater, then the command will issue a warning upon " - "completion. By default the streamlines for the output file are " - "extracted from the start of the input file(s); in this example the " - "command is instead instructed to skip the first 500 streamlines, and " - "write to the output file streamlines 501-1500.") - - + Example ("Extract streamlines based on selection criteria", - "tckedit in.tck out.tck -include ROI1.mif -include ROI2.mif -minlength 25", - "Multiple criteria can be added in a single invocation of tckedit, " - "and a streamline must satisfy all criteria imposed in order to be " - "written to the output file. Note that both -include and -exclude " - "options can be specified multiple times to provide multiple " - "waypoints / exclusion masks.") - - + Example ("Select only those streamline vertices within a mask", - "tckedit in.tck cropped.tck -mask mask.mif", - "The -mask option is applied to each streamline vertex independently, " - "rather than to each streamline, retaining only those streamline vertices " - "within the mask. As such, use of this option may result in a greater " - "number of output streamlines than input streamlines, as a single input " - "streamline may have the vertices at either endpoint retained but some " - "vertices at its midpoint removed, effectively cutting one long streamline " - "into multiple shorter streamlines."); - - + +Example("Concatenate data from multiple track files into one", + "tckedit *.tck all_tracks.tck", + "Here the wildcard operator is used to select all files in the " + "current working directory that have the .tck filetype suffix; but " + "input files can equivalently be specified one at a time explicitly.") + + + Example("Extract a reduced number of streamlines", + "tckedit in_many.tck out_few.tck -number 1k -skip 500", + "The number of streamlines requested would typically be less " + "than the number of streamlines in the input track file(s); if it " + "is instead greater, then the command will issue a warning upon " + "completion. By default the streamlines for the output file are " + "extracted from the start of the input file(s); in this example the " + "command is instead instructed to skip the first 500 streamlines, and " + "write to the output file streamlines 501-1500.") + + + Example("Extract streamlines based on selection criteria", + "tckedit in.tck out.tck -include ROI1.mif -include ROI2.mif -minlength 25", + "Multiple criteria can be added in a single invocation of tckedit, " + "and a streamline must satisfy all criteria imposed in order to be " + "written to the output file. Note that both -include and -exclude " + "options can be specified multiple times to provide multiple " + "waypoints / exclusion masks.") + + + Example("Select only those streamline vertices within a mask", + "tckedit in.tck cropped.tck -mask mask.mif", + "The -mask option is applied to each streamline vertex independently, " + "rather than to each streamline, retaining only those streamline vertices " + "within the mask. As such, use of this option may result in a greater " + "number of output streamlines than input streamlines, as a single input " + "streamline may have the vertices at either endpoint retained but some " + "vertices at its midpoint removed, effectively cutting one long streamline " + "into multiple shorter streamlines."); ARGUMENTS - + Argument ("tracks_in", "the input track file(s)").type_tracks_in().allow_multiple() - + Argument ("tracks_out", "the output track file").type_tracks_out(); + +Argument("tracks_in", "the input track file(s)").type_tracks_in().allow_multiple() + + Argument("tracks_out", "the output track file").type_tracks_out(); OPTIONS - + ROIOption - + LengthOption - + TruncateOption - + WeightsOption + +ROIOption + LengthOption + TruncateOption + WeightsOption - + OptionGroup ("Other options specific to tckedit") - + Option ("inverse", "output the inverse selection of streamlines based on the criteria provided; " - "i.e. only those streamlines that fail at least one selection criterion, " - "and/or vertices that are outside masks if provided, will be written to file") + + OptionGroup("Other options specific to tckedit") + + Option("inverse", + "output the inverse selection of streamlines based on the criteria provided; " + "i.e. only those streamlines that fail at least one selection criterion, " + "and/or vertices that are outside masks if provided, will be written to file") - + Option ("ends_only", "only test the ends of each streamline against the provided include/exclude ROIs") + + Option("ends_only", "only test the ends of each streamline against the provided include/exclude ROIs") - // TODO Input weights with multiple input files currently not supported - + OptionGroup ("Options for handling streamline weights") - + Tractography::TrackWeightsInOption - + Tractography::TrackWeightsOutOption; + // TODO Input weights with multiple input files currently not supported + + OptionGroup("Options for handling streamline weights") + Tractography::TrackWeightsInOption + + Tractography::TrackWeightsOutOption; // TODO Additional options? // - Peak curvature threshold // - Mean curvature threshold - } - -void erase_if_present (Tractography::Properties& p, const std::string s) -{ - auto i = p.find (s); +void erase_if_present(Tractography::Properties &p, const std::string s) { + auto i = p.find(s); if (i != p.end()) - p.erase (i); + p.erase(i); } - - -void run () -{ +void run() { const size_t num_inputs = argument.size() - 1; const std::string output_path = argument[num_inputs]; // Make sure configuration is sensible if (get_options("tck_weights_in").size() && num_inputs > 1) - throw Exception ("Cannot use per-streamline weighting with multiple input files"); + throw Exception("Cannot use per-streamline weighting with multiple input files"); // Get the consensus streamline properties from among the multiple input files Tractography::Properties properties; @@ -147,84 +130,78 @@ void run () for (size_t file_index = 0; file_index != num_inputs; ++file_index) { - input_file_list.push_back (argument[file_index]); + input_file_list.push_back(argument[file_index]); Properties p; - Reader (argument[file_index], p); + Reader(argument[file_index], p); - for (const auto& i : p.comments) { + for (const auto &i : p.comments) { bool present = false; - for (const auto& j: properties.comments) - if ( (present = (i == j)) ) + for (const auto &j : properties.comments) + if ((present = (i == j))) break; if (!present) - properties.comments.push_back (i); + properties.comments.push_back(i); } - for (const auto& i : p.prior_rois) { - const auto potential_matches = properties.prior_rois.equal_range (i.first); + for (const auto &i : p.prior_rois) { + const auto potential_matches = properties.prior_rois.equal_range(i.first); bool present = false; for (auto j = potential_matches.first; !present && j != potential_matches.second; ++j) present = (i.second == j->second); if (!present) - properties.prior_rois.insert (i); + properties.prior_rois.insert(i); } size_t this_count = 0; - for (const auto& i : p) { + for (const auto &i : p) { if (i.first == "count") { - this_count = to (i.second); + this_count = to(i.second); } else { - auto existing = properties.find (i.first); + auto existing = properties.find(i.first); if (existing == properties.end()) - properties.insert (i); + properties.insert(i); else if (i.second != existing->second) existing->second = "variable"; } } count += this_count; - } - DEBUG ("estimated number of input tracks: " + str(count)); + DEBUG("estimated number of input tracks: " + str(count)); // Remove keyval "total_count", as there is ambiguity about what _should_ be // contained in that field upon editing one or more existing tractograms // (it has a specific interpretation in the context of streamline generation only) - erase_if_present (properties, "total_count"); + erase_if_present(properties, "total_count"); - load_rois (properties); + load_rois(properties); properties.compare_stepsize_rois(); // Some properties from tracking may be overwritten by this editing process // Due to the potential use of masking, we have no choice but to clear the // properties class of any fields that would otherwise propagate through // and be applied as part of this editing - erase_if_present (properties, "min_dist"); - erase_if_present (properties, "max_dist"); - erase_if_present (properties, "min_weight"); - erase_if_present (properties, "max_weight"); - Editing::load_properties (properties); + erase_if_present(properties, "min_dist"); + erase_if_present(properties, "max_dist"); + erase_if_present(properties, "min_weight"); + erase_if_present(properties, "max_weight"); + Editing::load_properties(properties); // Parameters that the worker threads need to be aware of, but do not appear in Properties - const bool inverse = get_options ("inverse").size(); - const bool ends_only = get_options ("ends_only").size(); + const bool inverse = get_options("inverse").size(); + const bool ends_only = get_options("ends_only").size(); // Parameters that the output thread needs to be aware of - const size_t number = get_option_value ("number", size_t(0)); - const size_t skip = get_option_value ("skip", size_t(0)); - - Loader loader (input_file_list); - Worker worker (properties, inverse, ends_only); - Receiver receiver (output_path, properties, number, skip); + const size_t number = get_option_value("number", size_t(0)); + const size_t skip = get_option_value("skip", size_t(0)); - Thread::run_ordered_queue ( - loader, - Thread::batch (Streamline<>()), - Thread::multi (worker), - Thread::batch (Streamline<>()), - receiver); + Loader loader(input_file_list); + Worker worker(properties, inverse, ends_only); + Receiver receiver(output_path, properties, number, skip); + Thread::run_ordered_queue( + loader, Thread::batch(Streamline<>()), Thread::multi(worker), Thread::batch(Streamline<>()), receiver); } diff --git a/cmd/tckgen.cpp b/cmd/tckgen.cpp index 6ace7e0715..78df6cbf48 100644 --- a/cmd/tckgen.cpp +++ b/cmd/tckgen.cpp @@ -37,193 +37,184 @@ #include "dwi/tractography/seeding/seeding.h" - - - using namespace MR; using namespace App; +const char *algorithms[] = { + "fact", "ifod1", "ifod2", "nulldist1", "nulldist2", "sd_stream", "seedtest", "tensor_det", "tensor_prob", nullptr}; - -const char* algorithms[] = { "fact", "ifod1", "ifod2", "nulldist1", "nulldist2", "sd_stream", "seedtest", "tensor_det", "tensor_prob", nullptr }; - - -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com) and Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Perform streamlines tractography"; DESCRIPTION - + "By default, tckgen produces a fixed number of streamlines, by attempting " - "to seed from new random locations until the target number of " - "streamlines have been selected (in other words, after all inclusion & " - "exclusion criteria have been applied), or the maximum number of seeds " - "has been exceeded (by default, this is 1000 x the desired number of selected " - "streamlines). Use the -select and/or -seeds options to modify as " - "required. See also the Seeding options section for alternative seeding " - "strategies." - - + "Below is a list of available tracking algorithms, the input image data " - "that they require, and a brief description of their behaviour:" - - + "- FACT: Fiber Assigned by Continuous Tracking. A deterministic algorithm that " - "takes as input a 4D image, with 3xN volumes, where N is the maximum number " - "of fiber orientations in a voxel. Each triplet of volumes represents a 3D " - "vector corresponding to a fiber orientation; the length of the vector " - "additionally indicates some measure of density or anisotropy. As streamlines " - "move from one voxel to another, the fiber orientation most collinear with the " - "streamline orientation is selected (i.e. there is no intra-voxel interpolation)." - - + "- iFOD1: First-order Integration over Fiber Orientation Distributions. A " - "probabilistic algorithm that takes as input a Fiber Orientation Distribution " - "(FOD) image represented in the Spherical Harmonic (SH) basis. At each " - "streamline step, random samples from the local (trilinear interpolated) FOD " - "are taken. A streamline is more probable to follow orientations where the FOD " - "amplitude is large; but it may also rarely traverse orientations with small " - "FOD amplitude." - - + "- iFOD2 (default): Second-order Integration over Fiber Orientation " - "Distributions. A probabilistic algorithm that takes as input a Fiber " - "Orientation Distribution (FOD) image represented in the Spherical Harmonic " - "(SH) basis. Candidate streamline paths (based on short curved \"arcs\") are " - "drawn, and the underlying (trilinear-interpolated) FOD amplitudes along those " - "arcs are sampled. A streamline is more probable to follow a path where the FOD " - "amplitudes along that path are large; but it may also rarely traverse " - "orientations where the FOD amplitudes are small, as long as the amplitude " - "remains above the FOD amplitude threshold along the entire path." - - + "- NullDist1 / NullDist2: Null Distribution tracking algorithms. These " - "probabilistic algorithms expect as input the same image that was used when " - "invoking the corresponding algorithm for which the null distribution is " - "sought. These algorithms generate streamlines based on random orientation " - "samples; that is, no image information relating to fiber orientations is used, " - "and streamlines trajectories are determined entirely from random sampling. " - "The NullDist2 algorithm is designed to be used in conjunction with iFOD2; " - "NullDist1 should be used in conjunction with any first-order algorithm." - - + "- SD_STREAM: Streamlines tractography based on Spherical Deconvolution (SD). " - "A deterministic algorithm that takes as input a Fiber Orientation Distribution " - "(FOD) image represented in the Spherical Harmonic (SH) basis. At each " - "streamline step, the local (trilinear-interpolated) FOD is sampled, and from " - "the current streamline tangent orientation, a Newton optimisation on the " - "sphere is performed in order to locate the orientation of the nearest FOD " - "amplitude peak." - - + "- SeedTest: A dummy streamlines algorithm used for testing streamline seeding " - "mechanisms. Any image can be used as input; the image will not be used in any " - "way. For each seed point generated by the seeding mechanism(s), a streamline " - "containing a single point corresponding to that seed location will be written " - "to the output track file." - - + "- Tensor_Det: A deterministic algorithm that takes as input a 4D " - "diffusion-weighted image (DWI) series. At each streamline step, the diffusion " - "tensor is fitted to the local (trilinear-interpolated) diffusion data, and " - "the streamline trajectory is determined as the principal eigenvector of that " - "tensor." - - + "- Tensor_Prob: A probabilistic algorithm that takes as input a 4D " - "diffusion-weighted image (DWI) series. Within each image voxel, a residual " - "bootstrap is performed to obtain a unique realisation of the DWI data in that " - "voxel for each streamline. These data are then sampled via trilinear " - "interpolation at each streamline step, the diffusion tensor model is fitted, " - "and the streamline follows the orientation of the principal eigenvector of " - "that tensor." - - + "Note that the behaviour of the -angle option varies slightly depending on the " - "order of integration: for any first-order method, this angle corresponds to the " - "deviation in streamline trajectory per step; for higher-order methods, this " - "corresponds to the change in underlying fibre orientation between the start and " - "end points of each step."; + +"By default, tckgen produces a fixed number of streamlines, by attempting " + "to seed from new random locations until the target number of " + "streamlines have been selected (in other words, after all inclusion & " + "exclusion criteria have been applied), or the maximum number of seeds " + "has been exceeded (by default, this is 1000 x the desired number of selected " + "streamlines). Use the -select and/or -seeds options to modify as " + "required. See also the Seeding options section for alternative seeding " + "strategies." + + + "Below is a list of available tracking algorithms, the input image data " + "that they require, and a brief description of their behaviour:" + + + "- FACT: Fiber Assigned by Continuous Tracking. A deterministic algorithm that " + "takes as input a 4D image, with 3xN volumes, where N is the maximum number " + "of fiber orientations in a voxel. Each triplet of volumes represents a 3D " + "vector corresponding to a fiber orientation; the length of the vector " + "additionally indicates some measure of density or anisotropy. As streamlines " + "move from one voxel to another, the fiber orientation most collinear with the " + "streamline orientation is selected (i.e. there is no intra-voxel interpolation)." + + + "- iFOD1: First-order Integration over Fiber Orientation Distributions. A " + "probabilistic algorithm that takes as input a Fiber Orientation Distribution " + "(FOD) image represented in the Spherical Harmonic (SH) basis. At each " + "streamline step, random samples from the local (trilinear interpolated) FOD " + "are taken. A streamline is more probable to follow orientations where the FOD " + "amplitude is large; but it may also rarely traverse orientations with small " + "FOD amplitude." + + + "- iFOD2 (default): Second-order Integration over Fiber Orientation " + "Distributions. A probabilistic algorithm that takes as input a Fiber " + "Orientation Distribution (FOD) image represented in the Spherical Harmonic " + "(SH) basis. Candidate streamline paths (based on short curved \"arcs\") are " + "drawn, and the underlying (trilinear-interpolated) FOD amplitudes along those " + "arcs are sampled. A streamline is more probable to follow a path where the FOD " + "amplitudes along that path are large; but it may also rarely traverse " + "orientations where the FOD amplitudes are small, as long as the amplitude " + "remains above the FOD amplitude threshold along the entire path." + + + "- NullDist1 / NullDist2: Null Distribution tracking algorithms. These " + "probabilistic algorithms expect as input the same image that was used when " + "invoking the corresponding algorithm for which the null distribution is " + "sought. These algorithms generate streamlines based on random orientation " + "samples; that is, no image information relating to fiber orientations is used, " + "and streamlines trajectories are determined entirely from random sampling. " + "The NullDist2 algorithm is designed to be used in conjunction with iFOD2; " + "NullDist1 should be used in conjunction with any first-order algorithm." + + + "- SD_STREAM: Streamlines tractography based on Spherical Deconvolution (SD). " + "A deterministic algorithm that takes as input a Fiber Orientation Distribution " + "(FOD) image represented in the Spherical Harmonic (SH) basis. At each " + "streamline step, the local (trilinear-interpolated) FOD is sampled, and from " + "the current streamline tangent orientation, a Newton optimisation on the " + "sphere is performed in order to locate the orientation of the nearest FOD " + "amplitude peak." + + + "- SeedTest: A dummy streamlines algorithm used for testing streamline seeding " + "mechanisms. Any image can be used as input; the image will not be used in any " + "way. For each seed point generated by the seeding mechanism(s), a streamline " + "containing a single point corresponding to that seed location will be written " + "to the output track file." + + + "- Tensor_Det: A deterministic algorithm that takes as input a 4D " + "diffusion-weighted image (DWI) series. At each streamline step, the diffusion " + "tensor is fitted to the local (trilinear-interpolated) diffusion data, and " + "the streamline trajectory is determined as the principal eigenvector of that " + "tensor." + + + "- Tensor_Prob: A probabilistic algorithm that takes as input a 4D " + "diffusion-weighted image (DWI) series. Within each image voxel, a residual " + "bootstrap is performed to obtain a unique realisation of the DWI data in that " + "voxel for each streamline. These data are then sampled via trilinear " + "interpolation at each streamline step, the diffusion tensor model is fitted, " + "and the streamline follows the orientation of the principal eigenvector of " + "that tensor." + + + "Note that the behaviour of the -angle option varies slightly depending on the " + "order of integration: for any first-order method, this angle corresponds to the " + "deviation in streamline trajectory per step; for higher-order methods, this " + "corresponds to the change in underlying fibre orientation between the start and " + "end points of each step."; REFERENCES - + "References based on streamlines algorithm used:" - - + "* FACT:\n" - "Mori, S.; Crain, B. J.; Chacko, V. P. & van Zijl, P. C. M. " - "Three-dimensional tracking of axonal projections in the brain by magnetic resonance imaging. " - "Annals of Neurology, 1999, 45, 265-269" - - + "* iFOD1 or SD_STREAM:\n" - "Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal - "MRtrix: Diffusion tractography in crossing fiber regions. " - "Int. J. Imaging Syst. Technol., 2012, 22, 53-66" - - + "* iFOD2:\n" - "Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal - "Improved probabilistic streamlines tractography by 2nd order integration over fibre orientation distributions. " - "Proceedings of the International Society for Magnetic Resonance in Medicine, 2010, 1670" - - + "* Nulldist1 / Nulldist2:\n" - "Morris, D. M.; Embleton, K. V. & Parker, G. J. " - "Probabilistic fibre tracking: Differentiation of connections from chance events. " - "NeuroImage, 2008, 42, 1329-1339" - - + "* Tensor_Det:\n" - "Basser, P. J.; Pajevic, S.; Pierpaoli, C.; Duda, J. & Aldroubi, A. " - "In vivo fiber tractography using DT-MRI data. " - "Magnetic Resonance in Medicine, 2000, 44, 625-632" - - + "* Tensor_Prob:\n" - "Jones, D. " - "Tractography Gone Wild: Probabilistic Fibre Tracking Using the Wild Bootstrap With Diffusion Tensor MRI. " - "IEEE Transactions on Medical Imaging, 2008, 27, 1268-1274" - - + "References based on command-line options:" - - + "* -rk4:\n" - "Basser, P. J.; Pajevic, S.; Pierpaoli, C.; Duda, J. & Aldroubi, A. " - "In vivo fiber tractography using DT-MRI data. " - "Magnetic Resonance in Medicine, 2000, 44, 625-632" - - + "* -act, -backtrack, -seed_gmwmi:\n" - "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal - "Anatomically-constrained tractography: Improved diffusion MRI streamlines tractography through effective use of anatomical information. " - "NeuroImage, 2012, 62, 1924-1938" - - + "* -seed_dynamic:\n" - "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal - "SIFT2: Enabling dense quantitative assessment of brain white matter connectivity using streamlines tractography. " - "NeuroImage, 2015, 119, 338-351"; + +"References based on streamlines algorithm used:" + + + "* FACT:\n" + "Mori, S.; Crain, B. J.; Chacko, V. P. & van Zijl, P. C. M. " + "Three-dimensional tracking of axonal projections in the brain by magnetic resonance imaging. " + "Annals of Neurology, 1999, 45, 265-269" + + + "* iFOD1 or SD_STREAM:\n" + "Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal + "MRtrix: Diffusion tractography in crossing fiber regions. " + "Int. J. Imaging Syst. Technol., 2012, 22, 53-66" + + + + "* iFOD2:\n" + "Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal + "Improved probabilistic streamlines tractography by 2nd order integration over fibre orientation distributions. " + "Proceedings of the International Society for Magnetic Resonance in Medicine, 2010, 1670" + + + "* Nulldist1 / Nulldist2:\n" + "Morris, D. M.; Embleton, K. V. & Parker, G. J. " + "Probabilistic fibre tracking: Differentiation of connections from chance events. " + "NeuroImage, 2008, 42, 1329-1339" + + + "* Tensor_Det:\n" + "Basser, P. J.; Pajevic, S.; Pierpaoli, C.; Duda, J. & Aldroubi, A. " + "In vivo fiber tractography using DT-MRI data. " + "Magnetic Resonance in Medicine, 2000, 44, 625-632" + + + "* Tensor_Prob:\n" + "Jones, D. " + "Tractography Gone Wild: Probabilistic Fibre Tracking Using the Wild Bootstrap With Diffusion Tensor MRI. " + "IEEE Transactions on Medical Imaging, 2008, 27, 1268-1274" + + + "References based on command-line options:" + + + "* -rk4:\n" + "Basser, P. J.; Pajevic, S.; Pierpaoli, C.; Duda, J. & Aldroubi, A. " + "In vivo fiber tractography using DT-MRI data. " + "Magnetic Resonance in Medicine, 2000, 44, 625-632" + + + "* -act, -backtrack, -seed_gmwmi:\n" + "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal + "Anatomically-constrained tractography: Improved diffusion MRI streamlines tractography through effective use " + "of anatomical information. " + "NeuroImage, 2012, 62, 1924-1938" + + + "* -seed_dynamic:\n" + "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal + "SIFT2: Enabling dense quantitative assessment of brain white matter connectivity using streamlines " + "tractography. " + "NeuroImage, 2015, 119, 338-351"; ARGUMENTS - + Argument ("source", - "The image containing the source data. " - "The type of image data required depends on the algorithm used (see Description section).").type_image_in() - - + Argument ("tracks", "the output file containing the tracks generated.").type_tracks_out(); - + +Argument("source", + "The image containing the source data. " + "The type of image data required depends on the algorithm used (see Description section).") + .type_image_in() + + Argument("tracks", "the output file containing the tracks generated.").type_tracks_out(); OPTIONS - + Option ("algorithm", - "specify the tractography algorithm to use. Valid choices are: " - "FACT, iFOD1, iFOD2, Nulldist1, Nulldist2, SD_Stream, Seedtest, Tensor_Det, Tensor_Prob (default: iFOD2).") - + Argument ("name").type_choice (algorithms) + +Option("algorithm", + "specify the tractography algorithm to use. Valid choices are: " + "FACT, iFOD1, iFOD2, Nulldist1, Nulldist2, SD_Stream, Seedtest, Tensor_Det, Tensor_Prob (default: iFOD2).") + + Argument("name").type_choice(algorithms) - + DWI::Tractography::Tracking::TrackOption + + DWI::Tractography::Tracking::TrackOption - + DWI::Tractography::Seeding::SeedMechanismOption + + DWI::Tractography::Seeding::SeedMechanismOption - + DWI::Tractography::Seeding::SeedParameterOption + + DWI::Tractography::Seeding::SeedParameterOption - + DWI::Tractography::ROIOption + + DWI::Tractography::ROIOption - + DWI::Tractography::ACT::ACTOption + + DWI::Tractography::ACT::ACTOption - + DWI::Tractography::Algorithms::iFODOptions - + DWI::Tractography::Algorithms::iFOD2Options - - + DWI::GradImportOptions(); + + DWI::Tractography::Algorithms::iFODOptions + DWI::Tractography::Algorithms::iFOD2Options + + DWI::GradImportOptions(); } - - -void run () -{ +void run() { using namespace DWI::Tractography; using namespace DWI::Tractography::Tracking; @@ -232,26 +223,23 @@ void run () Properties properties; int algorithm = 2; // default = ifod2 - auto opt = get_options ("algorithm"); - if (opt.size()) algorithm = opt[0][0]; - - + auto opt = get_options("algorithm"); + if (opt.size()) + algorithm = opt[0][0]; + ACT::load_act_properties(properties); - ACT::load_act_properties (properties); - - Seeding::load_seed_mechanisms (properties); - Seeding::load_seed_parameters (properties); + Seeding::load_seed_mechanisms(properties); + Seeding::load_seed_parameters(properties); if (algorithm == 1 || algorithm == 2) - Algorithms::load_iFOD_options (properties); + Algorithms::load_iFOD_options(properties); if (algorithm == 2) - Algorithms::load_iFOD2_options (properties); - + Algorithms::load_iFOD2_options(properties); - //load ROIs and tractography specific options - //NB must occur before seed check below due to -select option override - Tracking::load_streamline_properties_and_rois (properties); + // load ROIs and tractography specific options + // NB must occur before seed check below due to -select option override + Tracking::load_streamline_properties_and_rois(properties); properties.compare_stepsize_rois(); // Check validity of options -select and -seeds; these are meaningless if seeds are number-limited @@ -259,47 +247,45 @@ void run () if (properties.seeds.is_finite()) { if (properties["max_num_tracks"].size()) - WARN ("Overriding -select option (desired number of successful streamline selections), as seeds can only provide a finite number"); - properties["max_num_tracks"] = str (properties.seeds.get_total_count()); + WARN("Overriding -select option (desired number of successful streamline selections), as seeds can only provide " + "a finite number"); + properties["max_num_tracks"] = str(properties.seeds.get_total_count()); if (properties["max_num_seeds"].size()) - WARN ("Overriding -seeds option (maximum number of seeds that will be attempted to track from), as seeds can only provide a finite number"); - properties["max_num_seeds"] = str (properties.seeds.get_total_count()); - + WARN("Overriding -seeds option (maximum number of seeds that will be attempted to track from), as seeds can only " + "provide a finite number"); + properties["max_num_seeds"] = str(properties.seeds.get_total_count()); } - - switch (algorithm) { - case 0: - Exec ::run (argument[0], argument[1], properties); - break; - case 1: - Exec ::run (argument[0], argument[1], properties); - break; - case 2: - Exec ::run (argument[0], argument[1], properties); - break; - case 3: - Exec ::run (argument[0], argument[1], properties); - break; - case 4: - Exec ::run (argument[0], argument[1], properties); - break; - case 5: - Exec ::run (argument[0], argument[1], properties); - break; - case 6: - Exec ::run (argument[0], argument[1], properties); - break; - case 7: - Exec ::run (argument[0], argument[1], properties); - break; - case 8: - Exec::run (argument[0], argument[1], properties); - break; - default: - assert (0); + case 0: + Exec::run(argument[0], argument[1], properties); + break; + case 1: + Exec::run(argument[0], argument[1], properties); + break; + case 2: + Exec::run(argument[0], argument[1], properties); + break; + case 3: + Exec::run(argument[0], argument[1], properties); + break; + case 4: + Exec::run(argument[0], argument[1], properties); + break; + case 5: + Exec::run(argument[0], argument[1], properties); + break; + case 6: + Exec::run(argument[0], argument[1], properties); + break; + case 7: + Exec::run(argument[0], argument[1], properties); + break; + case 8: + Exec::run(argument[0], argument[1], properties); + break; + default: + assert(0); } - } diff --git a/cmd/tckglobal.cpp b/cmd/tckglobal.cpp index 40e59b5234..cb05faaefe 100644 --- a/cmd/tckglobal.cpp +++ b/cmd/tckglobal.cpp @@ -18,24 +18,22 @@ #include -#include "image.h" -#include "thread.h" -#include "version.h" #include "algo/threaded_copy.h" #include "file/matrix.h" +#include "image.h" #include "math/SH.h" +#include "thread.h" +#include "version.h" -#include "dwi/tractography/GT/particlegrid.h" -#include "dwi/tractography/GT/gt.h" #include "dwi/tractography/GT/externalenergy.h" +#include "dwi/tractography/GT/gt.h" #include "dwi/tractography/GT/internalenergy.h" #include "dwi/tractography/GT/mhsampler.h" - +#include "dwi/tractography/GT/particlegrid.h" using namespace MR; using namespace App; - #define DEFAULT_LMAX 8 #define DEFAULT_LENGTH 1.0 #define DEFAULT_WEIGHT 0.1 @@ -56,10 +54,7 @@ using namespace App; #define DEFAULT_BETA 0.0 #define DEFAULT_LAMBDA 1.0 - - -void usage () -{ +void usage() { AUTHOR = "Daan Christiaens (daan.christiaens@kcl.ac.uk)"; @@ -72,152 +67,166 @@ void usage () SYNOPSIS = "Multi-Shell Multi-Tissue Global Tractography"; DESCRIPTION - + "This command will reconstruct the global white matter fibre tractogram that best " - "explains the input DWI data, using a multi-tissue spherical convolution model." + +"This command will reconstruct the global white matter fibre tractogram that best " + "explains the input DWI data, using a multi-tissue spherical convolution model." - + "A more thorough description of the operation of global tractography in MRtrix3 " - "can be found in the online documentation: \n" - "https://mrtrix.readthedocs.io/en/" MRTRIX_BASE_VERSION "/quantitative_structural_connectivity/global_tractography.html"; + + "A more thorough description of the operation of global tractography in MRtrix3 " + "can be found in the online documentation: \n" + "https://mrtrix.readthedocs.io/en/" MRTRIX_BASE_VERSION + "/quantitative_structural_connectivity/global_tractography.html"; EXAMPLES - + Example("Basic usage", - "tckglobal dwi.mif wmr.txt -riso csfr.txt -riso gmr.txt -mask mask.mif -niter 1e9 -fod fod.mif -fiso fiso.mif tracks.tck", - "dwi.mif is the input image, wmr.txt is an anisotropic, multi-shell response function for WM, " - "and csfr.txt and gmr.txt are isotropic response functions for CSF and GM. The output tractogram is " - "saved to tracks.tck. Optional output images fod.mif and fiso.mif contain the predicted WM fODF and " - "isotropic tissue fractions of CSF and GM respectively, estimated as part of the global optimization " - "and thus affected by spatial regularization."); + +Example("Basic usage", + "tckglobal dwi.mif wmr.txt -riso csfr.txt -riso gmr.txt -mask mask.mif -niter 1e9 -fod fod.mif -fiso " + "fiso.mif tracks.tck", + "dwi.mif is the input image, wmr.txt is an anisotropic, multi-shell response function for WM, " + "and csfr.txt and gmr.txt are isotropic response functions for CSF and GM. The output tractogram is " + "saved to tracks.tck. Optional output images fod.mif and fiso.mif contain the predicted WM fODF and " + "isotropic tissue fractions of CSF and GM respectively, estimated as part of the global optimization " + "and thus affected by spatial regularization."); REFERENCES - + "Christiaens, D.; Reisert, M.; Dhollander, T.; Sunaert, S.; Suetens, P. & Maes, F. " // Internal - "Global tractography of multi-shell diffusion-weighted imaging data using a multi-tissue model. " - "NeuroImage, 2015, 123, 89-101"; + +"Christiaens, D.; Reisert, M.; Dhollander, T.; Sunaert, S.; Suetens, P. & Maes, F. " // Internal + "Global tractography of multi-shell diffusion-weighted imaging data using a multi-tissue model. " + "NeuroImage, 2015, 123, 89-101"; ARGUMENTS - + Argument ("source", "the image containing the raw DWI data.").type_image_in() - - + Argument ("response", "the response of a track segment on the DWI signal.").type_file_in() - - + Argument ("tracks", "the output file containing the tracks generated.").type_tracks_out(); + +Argument("source", "the image containing the raw DWI data.").type_image_in() + + Argument("response", "the response of a track segment on the DWI signal.").type_file_in() + + Argument("tracks", "the output file containing the tracks generated.").type_tracks_out(); OPTIONS - + OptionGroup("Input options") - - + Option ("grad", "specify the diffusion encoding scheme (required if not supplied in the header).") - + Argument ("scheme").type_file_in() - - + Option ("mask", "only reconstruct the tractogram within the specified brain mask image.") - + Argument ("image").type_image_in() - - + Option ("riso", "set one or more isotropic response functions. (multiple allowed)").allow_multiple() - + Argument ("response").type_file_in() - - - + OptionGroup("Parameters") - - + Option ("lmax", "set the maximum harmonic order for the output series. (default = " + str(DEFAULT_LMAX) + ")") - + Argument ("order").type_integer (2, 30) - - + Option ("length", "set the length of the particles (fibre segments). (default = " + str(DEFAULT_LENGTH, 2) + "mm)") - + Argument ("size").type_float (1e-6) - - + Option ("weight", "set the weight by which particles contribute to the model. (default = " + str(DEFAULT_WEIGHT, 2) + ")") - + Argument ("w").type_float(1e-6, 1.0) - - + Option ("ppot", "set the particle potential, i.e., the cost of adding one segment, relative to the particle weight. (default = " + str(DEFAULT_PPOT, 2) + ")") - + Argument ("u").type_float(0.0, 1.0) - - + Option ("cpot", "set the connection potential, i.e., the energy term that drives two segments together. (default = " + str(DEFAULT_CPOT, 2) + ")") - + Argument ("v").type_float(0.0) - - + Option ("t0", "set the initial temperature of the metropolis hastings optimizer. (default = " + str(DEFAULT_T0, 2) + ")") - + Argument ("start").type_float(1e-6, 1e6) - - + Option ("t1", "set the final temperature of the metropolis hastings optimizer. (default = " + str(DEFAULT_T1, 2) + ")") - + Argument ("end").type_float(1e-6, 1e6) - - + Option ("niter", "set the number of iterations of the metropolis hastings optimizer. (default = " + str(DEFAULT_NITER/1000000) + "M)") - + Argument ("n").type_integer(0) - - - + OptionGroup("Output options") - - + Option ("fod", "Predicted fibre orientation distribution function (fODF).\n" - "This fODF is estimated as part of the global track optimization, " - "and therefore incorporates the spatial regularization that it " - "imposes. Internally, the fODF is represented as a discrete " - "sum of apodized point spread functions (aPSF) oriented along the " - "directions of all particles in the voxel, used to predict the DWI " - "signal from the particle configuration.") - + Argument ("odf").type_image_out() - + Option ("noapo", "disable spherical convolution of fODF with apodized PSF, " - "to output a sum of delta functions rather than a sum of aPSFs.") - - + Option ("fiso", "Predicted isotropic fractions of the tissues for which response " - "functions were provided with -riso. Typically, these are CSF and GM.") - + Argument ("iso").type_image_out() - - + Option ("eext", "Residual external energy in every voxel.") - + Argument ("eext").type_image_out() - - + Option ("etrend", "internal and external energy trend and cooling statistics.") - + Argument ("stats").type_file_out() - - - + OptionGroup("Advanced parameters, if you really know what you're doing") - - + Option ("balance", "balance internal and external energy. (default = " + str(DEFAULT_BALANCE, 2) + ")\n" - "Negative values give more weight to the internal energy, positive to the external energy.") - + Argument ("b").type_float(-100.0, 100.0) - - + Option ("density", "set the desired density of the free Poisson process. (default = " + str(DEFAULT_DENSITY, 2) + ")") - + Argument ("lambda").type_float(0.0) - - + Option ("prob", "set the probabilities of generating birth, death, randshift, optshift " - "and connect proposals respectively. (default = " - + str(DEFAULT_PROB_BIRTH, 2) + "," + str(DEFAULT_PROB_DEATH, 2) + "," - + str(DEFAULT_PROB_RANDSHIFT, 2) + "," + str(DEFAULT_PROB_OPTSHIFT, 2) + "," - + str(DEFAULT_PROB_CONNECT, 2) + ")") - + Argument ("prob").type_sequence_float() - - + Option ("beta", "set the width of the Hanning interpolation window. (in [0, 1], default = " + str(DEFAULT_BETA, 2) + ")\n" - "If used, a mask is required, and this mask must keep at least one voxel distance to the image bounding box.") - + Argument ("b").type_float (0.0, 1.0) - - + Option ("lambda", "set the weight of the internal energy directly. (default = " + str(DEFAULT_LAMBDA, 2) + ")\n" - "If provided, any value of -balance will be ignored.") - + Argument ("lam").type_float(0.0); - + +OptionGroup("Input options") + + + Option("grad", "specify the diffusion encoding scheme (required if not supplied in the header).") + + Argument("scheme").type_file_in() + + + Option("mask", "only reconstruct the tractogram within the specified brain mask image.") + + Argument("image").type_image_in() + + + Option("riso", "set one or more isotropic response functions. (multiple allowed)").allow_multiple() + + Argument("response").type_file_in() + + + OptionGroup("Parameters") + + + Option("lmax", "set the maximum harmonic order for the output series. (default = " + str(DEFAULT_LMAX) + ")") + + Argument("order").type_integer(2, 30) + + + Option("length", + "set the length of the particles (fibre segments). (default = " + str(DEFAULT_LENGTH, 2) + "mm)") + + Argument("size").type_float(1e-6) + + + + Option("weight", + "set the weight by which particles contribute to the model. (default = " + str(DEFAULT_WEIGHT, 2) + ")") + + Argument("w").type_float(1e-6, 1.0) + + + Option("ppot", + "set the particle potential, i.e., the cost of adding one segment, relative to the particle weight. " + "(default = " + + str(DEFAULT_PPOT, 2) + ")") + + Argument("u").type_float(0.0, 1.0) + + + Option("cpot", + "set the connection potential, i.e., the energy term that drives two segments together. (default = " + + str(DEFAULT_CPOT, 2) + ")") + + Argument("v").type_float(0.0) + + + Option("t0", + "set the initial temperature of the metropolis hastings optimizer. (default = " + str(DEFAULT_T0, 2) + + ")") + + Argument("start").type_float(1e-6, 1e6) + + + + Option("t1", + "set the final temperature of the metropolis hastings optimizer. (default = " + str(DEFAULT_T1, 2) + ")") + + Argument("end").type_float(1e-6, 1e6) + + + Option("niter", + "set the number of iterations of the metropolis hastings optimizer. (default = " + + str(DEFAULT_NITER / 1000000) + "M)") + + Argument("n").type_integer(0) + + + OptionGroup("Output options") + + + Option("fod", + "Predicted fibre orientation distribution function (fODF).\n" + "This fODF is estimated as part of the global track optimization, " + "and therefore incorporates the spatial regularization that it " + "imposes. Internally, the fODF is represented as a discrete " + "sum of apodized point spread functions (aPSF) oriented along the " + "directions of all particles in the voxel, used to predict the DWI " + "signal from the particle configuration.") + + Argument("odf").type_image_out() + + Option("noapo", + "disable spherical convolution of fODF with apodized PSF, " + "to output a sum of delta functions rather than a sum of aPSFs.") + + + Option("fiso", + "Predicted isotropic fractions of the tissues for which response " + "functions were provided with -riso. Typically, these are CSF and GM.") + + Argument("iso").type_image_out() + + + Option("eext", "Residual external energy in every voxel.") + Argument("eext").type_image_out() + + + Option("etrend", "internal and external energy trend and cooling statistics.") + + Argument("stats").type_file_out() + + + OptionGroup("Advanced parameters, if you really know what you're doing") + + + Option("balance", + "balance internal and external energy. (default = " + str(DEFAULT_BALANCE, 2) + + ")\n" + "Negative values give more weight to the internal energy, positive to the external energy.") + + Argument("b").type_float(-100.0, 100.0) + + + Option("density", + "set the desired density of the free Poisson process. (default = " + str(DEFAULT_DENSITY, 2) + ")") + + Argument("lambda").type_float(0.0) + + + + Option("prob", + "set the probabilities of generating birth, death, randshift, optshift " + "and connect proposals respectively. (default = " + + str(DEFAULT_PROB_BIRTH, 2) + "," + str(DEFAULT_PROB_DEATH, 2) + "," + str(DEFAULT_PROB_RANDSHIFT, 2) + + "," + str(DEFAULT_PROB_OPTSHIFT, 2) + "," + str(DEFAULT_PROB_CONNECT, 2) + ")") + + Argument("prob").type_sequence_float() + + + Option("beta", + "set the width of the Hanning interpolation window. (in [0, 1], default = " + str(DEFAULT_BETA, 2) + + ")\n" + "If used, a mask is required, and this mask must keep at least one voxel distance to the image " + "bounding box.") + + Argument("b").type_float(0.0, 1.0) + + + Option("lambda", + "set the weight of the internal energy directly. (default = " + str(DEFAULT_LAMBDA, 2) + + ")\n" + "If provided, any value of -balance will be ignored.") + + Argument("lam").type_float(0.0); } +template class __copy_fod { +public: + __copy_fod(const int lmax, const double weight, const bool apodise) + : w(weight), a(apodise), apo(lmax), SH_in(Math::SH::NforL(lmax)), SH_out(SH_in.size()) {} -template -class __copy_fod { - public: - __copy_fod (const int lmax, const double weight, const bool apodise) - : w(weight), a(apodise), apo (lmax), SH_in (Math::SH::NforL(lmax)), SH_out (SH_in.size()) { } - - void operator() (Image& in, Image& out) { - SH_in = in.row(3); - out.row(3) = w * (a ? Math::SH::sconv (SH_out, apo.RH_coefs(), SH_in) : SH_in); - } - - private: - T w; - bool a; - Math::SH::aPSF apo; - Eigen::Matrix SH_in, SH_out; + void operator()(Image &in, Image &out) { + SH_in = in.row(3); + out.row(3) = w * (a ? Math::SH::sconv(SH_out, apo.RH_coefs(), SH_in) : SH_in); + } +private: + T w; + bool a; + Math::SH::aPSF apo; + Eigen::Matrix SH_in, SH_out; }; - - - -void run () -{ +void run() { using namespace DWI::Tractography::GT; @@ -226,13 +235,12 @@ void run () auto dwi = Image::open(argument[0]).with_direct_io(3); Properties properties; - properties.resp_WM = File::Matrix::load_matrix (argument[1]); - double wmscale2 = (properties.resp_WM(0,0)*properties.resp_WM(0,0))/M_4PI; + properties.resp_WM = File::Matrix::load_matrix(argument[1]); + double wmscale2 = (properties.resp_WM(0, 0) * properties.resp_WM(0, 0)) / M_4PI; Eigen::VectorXf riso; auto opt = get_options("riso"); - for (auto popt : opt) - { + for (auto popt : opt) { riso = File::Matrix::load_vector(popt[0]); properties.resp_ISO.push_back(riso); } @@ -262,17 +270,15 @@ void run () properties.beta = get_option_value("beta", DEFAULT_BETA); opt = get_options("balance"); - if (opt.size()) - { + if (opt.size()) { double lam = opt[0][0]; double b = 1.0 / (1.0 + exp(-lam)); - properties.lam_ext = 2*b; - properties.lam_int = 2*(1-b); + properties.lam_ext = 2 * b; + properties.lam_int = 2 * (1 - b); } opt = get_options("prob"); - if (opt.size()) - { + if (opt.size()) { auto prob = opt[0][0].as_sequence_float(); if (prob.size() == 5) { properties.p_birth = prob[0]; @@ -298,35 +304,39 @@ void run () properties.lam_int = opt[0][0]; } - // Prepare data structures ------------------------------------------------------------ INFO("Initialise data structures for global tractography."); - Stats stats (t0, t1, niter); + Stats stats(t0, t1, niter); opt = get_options("etrend"); if (opt.size()) stats.open_stream(opt[0][0]); - ParticleGrid pgrid (dwi); + ParticleGrid pgrid(dwi); - ExternalEnergyComputer* Eext = new ExternalEnergyComputer(stats, dwi, properties); - InternalEnergyComputer* Eint = new InternalEnergyComputer(stats, pgrid); + ExternalEnergyComputer *Eext = new ExternalEnergyComputer(stats, dwi, properties); + InternalEnergyComputer *Eint = new InternalEnergyComputer(stats, pgrid); Eint->setConnPot(cpot); - EnergySumComputer* Esum = new EnergySumComputer(stats, Eint, properties.lam_int, Eext, properties.lam_ext / ( wmscale2 * properties.weight*properties.weight)); - - MHSampler mhs (dwi, properties, stats, pgrid, Esum, mask); // All EnergyComputers are recursively destroyed upon destruction of mhs, except for the shared data. + EnergySumComputer *Esum = new EnergySumComputer( + stats, Eint, properties.lam_int, Eext, properties.lam_ext / (wmscale2 * properties.weight * properties.weight)); + MHSampler mhs( + dwi, + properties, + stats, + pgrid, + Esum, + mask); // All EnergyComputers are recursively destroyed upon destruction of mhs, except for the shared data. INFO("Start MH sampler"); - Thread::run (Thread::multi(mhs), "MH sampler"); + Thread::run(Thread::multi(mhs), "MH sampler"); INFO("Final no. particles: " + std::to_string(pgrid.getTotalCount())); INFO("Final external energy: " + std::to_string(stats.getEextTotal())); INFO("Final internal energy: " + std::to_string(stats.getEintTotal())); - // Copy results to output buffers ----------------------------------------------------- // Save the tracks (output) @@ -334,29 +344,28 @@ void run () MR::DWI::Tractography::Properties ftfileprops; ftfileprops.comments.push_back("global tractography"); ftfileprops.comments.push_back(""); - ftfileprops.comments.push_back("segment length = " + std::to_string((long double) Particle::L)); - ftfileprops.comments.push_back("segment weight = " + std::to_string((long double) properties.weight)); + ftfileprops.comments.push_back("segment length = " + std::to_string((long double)Particle::L)); + ftfileprops.comments.push_back("segment weight = " + std::to_string((long double)properties.weight)); ftfileprops.comments.push_back(""); - ftfileprops.comments.push_back("connection potential = " + std::to_string((long double) cpot)); - ftfileprops.comments.push_back("particle potential = " + std::to_string((long double) mu)); + ftfileprops.comments.push_back("connection potential = " + std::to_string((long double)cpot)); + ftfileprops.comments.push_back("particle potential = " + std::to_string((long double)mu)); ftfileprops.comments.push_back(""); - ftfileprops.comments.push_back("no. iterations = " + std::to_string((long long int) niter)); - ftfileprops.comments.push_back("T0 = " + std::to_string((long double) t0)); - ftfileprops.comments.push_back("T1 = " + std::to_string((long double) t1)); + ftfileprops.comments.push_back("no. iterations = " + std::to_string((long long int)niter)); + ftfileprops.comments.push_back("T0 = " + std::to_string((long double)t0)); + ftfileprops.comments.push_back("T1 = " + std::to_string((long double)t1)); - MR::DWI::Tractography::Writer writer (argument[2], ftfileprops); + MR::DWI::Tractography::Writer writer(argument[2], ftfileprops); pgrid.exportTracks(writer); - // Save fiso, tod and eext - Header header (dwi); + Header header(dwi); header.datatype() = DataType::Float32; opt = get_options("fod"); if (opt.size()) { INFO("Saving fODF image to file"); header.size(3) = Math::SH::NforL(properties.Lmax); - auto fODF = Image::create (opt[0][0], header); + auto fODF = Image::create(opt[0][0], header); auto f = __copy_fod(properties.Lmax, properties.weight, !get_options("noapo").size()); ThreadedLoop(Eext->getTOD(), 0, 3).run(f, Eext->getTOD(), fODF); } @@ -366,10 +375,9 @@ void run () if (properties.resp_ISO.size() > 0) { INFO("Saving isotropic fractions to file"); header.size(3) = properties.resp_ISO.size(); - auto Fiso = Image::create (opt[0][0], header); + auto Fiso = Image::create(opt[0][0], header); threaded_copy(Eext->getFiso(), Fiso); - } - else { + } else { WARN("Ignore saving file " + opt[0][0] + ", because no isotropic response functions were provided."); } } @@ -378,12 +386,7 @@ void run () if (opt.size()) { INFO("Saving external energy to file"); header.ndim() = 3; - auto EextI = Image::create (opt[0][0], header); + auto EextI = Image::create(opt[0][0], header); threaded_copy(Eext->getEext(), EextI); } - - - - } - diff --git a/cmd/tckinfo.cpp b/cmd/tckinfo.cpp index 4b7d233801..064aad286f 100644 --- a/cmd/tckinfo.cpp +++ b/cmd/tckinfo.cpp @@ -15,47 +15,41 @@ */ #include "command.h" -#include "progressbar.h" -#include "file/ofstream.h" #include "dwi/tractography/file.h" #include "dwi/tractography/properties.h" +#include "file/ofstream.h" +#include "progressbar.h" using namespace MR; using namespace MR::DWI; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Print out information about a track file"; ARGUMENTS - + Argument ("tracks", "the input track file.").type_tracks_in().allow_multiple(); + +Argument("tracks", "the input track file.").type_tracks_in().allow_multiple(); OPTIONS - + Option ("count", "count number of tracks in file explicitly, ignoring the header"); - + +Option("count", "count number of tracks in file explicitly, ignoring the header"); } - - - -void run () -{ - bool actual_count = get_options ("count").size(); +void run() { + bool actual_count = get_options("count").size(); for (size_t i = 0; i < argument.size(); ++i) { Tractography::Properties properties; - Tractography::Reader file (argument[i], properties); + Tractography::Reader file(argument[i], properties); std::cout << "***********************************\n"; std::cout << " Tracks file: \"" << argument[i] << "\"\n"; for (Tractography::Properties::iterator i = properties.begin(); i != properties.end(); ++i) { - std::string S (i->first + ':'); - S.resize (22, ' '); - const auto lines = split_lines (i->second); + std::string S(i->first + ':'); + S.resize(22, ' '); + const auto lines = split_lines(i->second); std::cout << " " << S << lines[0] << "\n"; for (size_t i = 1; i != lines.size(); ++i) std::cout << " " << lines[i] << "\n"; @@ -67,24 +61,22 @@ void run () std::cout << (i == properties.comments.begin() ? "" : " ") << *i << "\n"; } - for (std::multimap::const_iterator i = properties.prior_rois.begin(); i != properties.prior_rois.end(); ++i) + for (std::multimap::const_iterator i = properties.prior_rois.begin(); + i != properties.prior_rois.end(); + ++i) std::cout << " ROI: " << i->first << " " << i->second << "\n"; - - if (actual_count) { Tractography::Streamline tck; size_t count = 0; { - ProgressBar progress ("counting tracks in file"); - while (file (tck)) { + ProgressBar progress("counting tracks in file"); + while (file(tck)) { ++count; ++progress; } } std::cout << "actual count in file: " << count << "\n"; } - - } } diff --git a/cmd/tckmap.cpp b/cmd/tckmap.cpp index 25e565bc97..d3332b03c5 100644 --- a/cmd/tckmap.cpp +++ b/cmd/tckmap.cpp @@ -37,9 +37,6 @@ #include "dwi/tractography/mapping/gaussian/mapper.h" #include "dwi/tractography/mapping/gaussian/voxel.h" - - - using namespace MR; using namespace App; @@ -47,279 +44,243 @@ 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") - - - -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 = "Map streamlines to an image, with various options for generating image contrast"; - -DESCRIPTION - - + "The -contrast option controls how a value is derived for each streamline that is " - "subsequently contributed to the image elements intersected by that streamline, and " - "therefore strongly influences the contrast of that image. The permissible values " - "are briefly summarised as follows: " - + "- tdi: Each streamline effectively contributes a value of unity to the final map " - "(equivalent to the original Track Density Imaging (TDI) method)" - + "- length: The length of the streamline in mm" - + "- invlength: The reciprocal of streamline length" - + "- scalar_map: Values are sampled from a scalar image (which must be provided via -image)" - + "- scalar_map_count: If a non-zero value is sampled from a scalar image (as provided " + + 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 = "Map streamlines to an image, with various options for generating image contrast"; + + DESCRIPTION + + +"The -contrast option controls how a value is derived for each streamline that is " + "subsequently contributed to the image elements intersected by that streamline, and " + "therefore strongly influences the contrast of that image. The permissible values " + "are briefly summarised as follows: " + + "- tdi: Each streamline effectively contributes a value of unity to the final map " + "(equivalent to the original Track Density Imaging (TDI) method)" + + "- length: The length of the streamline in mm" + "- invlength: The reciprocal of streamline length" + + "- scalar_map: Values are sampled from a scalar image (which must be provided via -image)" + + "- scalar_map_count: If a non-zero value is sampled from a scalar image (as provided " "via -image), the streamline contributes a value of 1, otherwise it contributes 0, such " "that an image can be produced reflecting the density of streamlines that intersect such " - "an image" - + "- fod_amp: The amplitudes of a Fibre Orientation Distribution (FOD) image" - + "- curvature: The curvature of the streamline" - + "- vector_file: A value for each streamline has been pre-calculated, and these are " + "an image" + + "- fod_amp: The amplitudes of a Fibre Orientation Distribution (FOD) image" + + "- curvature: The curvature of the streamline" + + "- vector_file: A value for each streamline has been pre-calculated, and these are " "provided in a text file via the -vector_file option" - + "A \"super-resolution\" output image can be generated using the -vox option, whether " - "or not a template image is provided using the -template option. If -template is used " - "in conjunction with -vox, the image axes and FoV will still match that of the template " - "image, but the spatial resolution will differ." - - + "Note: if you run into limitations with RAM usage, try writing the output image " - "as a .mif file or .mih / .dat file pair to a local hard drive: this will allow " - "tckmap to dump the generated image contents directly to disk, rather than allocating " - "an additional buffer to store the output image for write-out, thereby potentially " - "halving RAM usage."; - -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; - + + "A \"super-resolution\" output image can be generated using the -vox option, whether " + "or not a template image is provided using the -template option. If -template is used " + "in conjunction with -vox, the image axes and FoV will still match that of the template " + "image, but the spatial resolution will differ." + + + "Note: if you run into limitations with RAM usage, try writing the output image " + "as a .mif file or .mih / .dat file pair to a local hard drive: this will allow " + "tckmap to dump the generated image contents directly to disk, rather than allocating " + "an additional buffer to store the output image for write-out, thereby potentially " + "halving RAM usage."; + + 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; +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 (H, name, stat_vox, dim); + writer = new MapWriter(H, name, stat_vox, dim); else if (dt == DataType::UInt8) - writer = new MapWriter (H, name, stat_vox, dim); + writer = new MapWriter(H, name, stat_vox, dim); else if (dt == DataType::UInt16) - writer = new MapWriter (H, name, stat_vox, dim); + writer = new MapWriter(H, name, stat_vox, dim); else if (dt == DataType::UInt32 || dt == DataType::UInt64) - writer = new MapWriter (H, name, stat_vox, dim); + writer = new MapWriter(H, name, stat_vox, dim); else if (dt == DataType::Float32 || dt == DataType::Float64) - writer = new MapWriter (H, name, stat_vox, dim); + writer = new MapWriter(H, name, stat_vox, dim); else - throw Exception ("Unsupported data type in image header"); + 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) -{ +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())); + 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 () { +void run() { Tractography::Properties properties; - Tractography::Reader file (argument[0], properties); + Tractography::Reader file(argument[0], properties); - const size_t num_tracks = properties["count"].empty() ? 0 : to (properties["count"]); + const size_t num_tracks = properties["count"].empty() ? 0 : to(properties["count"]); - vector voxel_size = get_option_value ("vox", vector()); + vector voxel_size = get_option_value("vox", vector()); if (voxel_size.size() == 1) { - auto v = voxel_size.front(); - voxel_size.assign (3, v); + 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"); + 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]) + " ]"); + 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"); + auto opt = get_options("template"); if (opt.size()) { - auto template_header = Header::open (opt[0][0]); + 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 { + 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); + throw Exception("please specify a template image and/or the desired voxel size"); + generate_header(header, argument[0], voxel_size); } if (header.ndim() > 3) { @@ -327,226 +288,238 @@ void run () { header.sanitise(); } - add_line (header.keyval()["comments"], "track-weighted image"); - header.keyval()["tck_source"] = std::string (argument[0]); + add_line(header.keyval()["comments"], "track-weighted image"); + header.keyval()["tck_source"] = std::string(argument[0]); - opt = get_options ("contrast"); + opt = get_options("contrast"); const contrast_t contrast = opt.size() ? contrast_t(int(opt[0][0])) : TDI; - opt = get_options ("stat_vox"); + 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"); + 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"); + 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."); + 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"); + 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) + 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"); + 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"); + opt = get_options("dec"); if (opt.size()) { writer_type = DEC; header.ndim() = 4; - header.size (3) = 3; + header.size(3) = 3; header.sanitise(); - Stride::set (header, Stride::contiguous_along_axis (3, header)); + Stride::set(header, Stride::contiguous_along_axis(3, header)); } std::unique_ptr dirs; - opt = get_options ("dixel"); + opt = get_options("dixel"); if (opt.size()) { if (writer_type != GREYSCALE) - throw Exception ("Options for setting output image dimensionality are mutually exclusive"); + 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]))); + if (Path::exists(opt[0][0])) + dirs.reset(new Directions::FastLookupSet(str(opt[0][0]))); else - dirs.reset (new Directions::FastLookupSet (to(opt[0][0]))); + dirs.reset(new Directions::FastLookupSet(to(opt[0][0]))); header.ndim() = 4; header.size(3) = dirs->size(); header.sanitise(); - Stride::set (header, Stride::contiguous_along_axis (3, header)); + Stride::set(header, Stride::contiguous_along_axis(3, header)); // Write directions to image header as diffusion encoding - Eigen::MatrixXd grad (dirs->size(), 4); + 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; + 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); + set_DW_scheme(header, grad); } - opt = get_options ("tod"); + opt = get_options("tod"); if (opt.size()) { if (writer_type != GREYSCALE) - throw Exception ("Options for setting output image dimensionality are mutually exclusive"); + 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"); + throw Exception("lmax for TODI must be an even number"); header.ndim() = 4; - header.size(3) = Math::SH::NforL (lmax); + header.size(3) = Math::SH::NforL(lmax); header.sanitise(); - Stride::set (header, Stride::contiguous_along_axis (3, header)); + 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"); - + 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(); + const bool precise = get_options("precise").size(); header.keyval()["precise_mapping"] = precise ? "1" : "0"; - const bool ends_only = get_options ("ends_only").size(); + const bool ends_only = get_options("ends_only").size(); if (ends_only) { if (precise) - throw Exception ("Options -precise and -ends_only are mutually exclusive"); + throw Exception("Options -precise and -ends_only are mutually exclusive"); header.keyval()["endpoints_only"] = "1"; } size_t upsample_ratio = 1; - opt = get_options ("upsample"); + opt = get_options("upsample"); if (opt.size()) { if (ends_only) { - WARN ("cannot use upsampling if only streamline endpoints are to be mapped"); + 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)); + 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)"); + 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"); + 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"); + 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]); + header.datatype() = DataType::parse(opt[0][0]); } } - const bool have_weights = get_options ("tck_weights_in").size(); + 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"); + 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)) + 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() = 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(); + 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; + 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) @@ -554,105 +527,198 @@ void run () { 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; + 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"); + 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); - + INFO(msg); // Start initialising members for multi-threaded calculation - TrackLoader loader (file, num_tracks); - - std::unique_ptr 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); + TrackLoader loader(file, num_tracks); + + std::unique_ptr 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); + mapper->create_dixel_plugin(*dirs); if (writer_type == TOD) - mapper->create_tod_plugin (header.size(3)); + mapper->create_tod_plugin(header.size(3)); if (contrast == SCALAR_MAP || contrast == SCALAR_MAP_COUNT || contrast == FOD_AMP) { - opt = get_options ("image"); + 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"); + 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"); + 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]); + const std::string assoc_image(opt[0][0]); if (contrast == SCALAR_MAP || contrast == SCALAR_MAP_COUNT) { - mapper->add_scalar_image (assoc_image); + mapper->add_scalar_image(assoc_image); if (backtrack) mapper->set_backtrack(); } else { - mapper->add_fod_image (assoc_image); + mapper->add_fod_image(assoc_image); } - header.keyval()["twi_assoc_image"] = Path::basename (assoc_image); + header.keyval()["twi_assoc_image"] = Path::basename(assoc_image); } else if (contrast == VECTOR_FILE) { - opt = get_options ("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); + 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 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 (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 (header, argument[1], stat_vox, TOD)); break; + 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(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(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(mapper.get()); - mapper_ptr->set_gaussian_FWHM (gaussian_fwhm_tck); + Gaussian::TrackMapper *const mapper_ptr = dynamic_cast(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()), Thread::multi (*mapper_ptr), Thread::batch (Gaussian::SetVoxel()), *writer); break; - case DEC: Thread::run_queue (loader, Thread::batch (Tractography::Streamline()), Thread::multi (*mapper_ptr), Thread::batch (Gaussian::SetVoxelDEC()), *writer); break; - case DIXEL: Thread::run_queue (loader, Thread::batch (Tractography::Streamline()), Thread::multi (*mapper_ptr), Thread::batch (Gaussian::SetDixel()), *writer); break; - case TOD: Thread::run_queue (loader, Thread::batch (Tractography::Streamline()), Thread::multi (*mapper_ptr), Thread::batch (Gaussian::SetVoxelTOD()), *writer); break; + case UNDEFINED: + throw Exception("Invalid TWI writer image dimensionality"); + case GREYSCALE: + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline()), + Thread::multi(*mapper_ptr), + Thread::batch(Gaussian::SetVoxel()), + *writer); + break; + case DEC: + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline()), + Thread::multi(*mapper_ptr), + Thread::batch(Gaussian::SetVoxelDEC()), + *writer); + break; + case DIXEL: + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline()), + Thread::multi(*mapper_ptr), + Thread::batch(Gaussian::SetDixel()), + *writer); + break; + case TOD: + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline()), + 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()), Thread::multi (*mapper), Thread::batch (SetVoxel()), *writer); break; - case DEC: Thread::run_queue (loader, Thread::batch (Tractography::Streamline()), Thread::multi (*mapper), Thread::batch (SetVoxelDEC()), *writer); break; - case DIXEL: Thread::run_queue (loader, Thread::batch (Tractography::Streamline()), Thread::multi (*mapper), Thread::batch (SetDixel()), *writer); break; - case TOD: Thread::run_queue (loader, Thread::batch (Tractography::Streamline()), Thread::multi (*mapper), Thread::batch (SetVoxelTOD()), *writer); break; + case UNDEFINED: + throw Exception("Invalid TWI writer image dimensionality"); + case GREYSCALE: + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline()), + Thread::multi(*mapper), + Thread::batch(SetVoxel()), + *writer); + break; + case DEC: + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline()), + Thread::multi(*mapper), + Thread::batch(SetVoxelDEC()), + *writer); + break; + case DIXEL: + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline()), + Thread::multi(*mapper), + Thread::batch(SetDixel()), + *writer); + break; + case TOD: + Thread::run_queue(loader, + Thread::batch(Tractography::Streamline()), + Thread::multi(*mapper), + Thread::batch(SetVoxelTOD()), + *writer); + break; } } writer->finalise(); } - - diff --git a/cmd/tckresample.cpp b/cmd/tckresample.cpp index 433e38bba0..66d9ac874c 100644 --- a/cmd/tckresample.cpp +++ b/cmd/tckresample.cpp @@ -15,10 +15,6 @@ */ #include "command.h" -#include "math/math.h" -#include "image.h" -#include "ordered_thread_queue.h" -#include "thread.h" #include "dwi/tractography/file.h" #include "dwi/tractography/properties.h" #include "dwi/tractography/resampling/arc.h" @@ -28,106 +24,92 @@ #include "dwi/tractography/resampling/fixed_step_size.h" #include "dwi/tractography/resampling/resampling.h" #include "dwi/tractography/resampling/upsampler.h" - - - +#include "image.h" +#include "math/math.h" +#include "ordered_thread_queue.h" +#include "thread.h" using namespace MR; using namespace App; using namespace DWI::Tractography; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au) and J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Resample each streamline in a track file to a new set of vertices"; DESCRIPTION - + "It is necessary to specify precisely ONE of the command-line options for " - "controlling how this resampling takes place; this may be either increasing " - "or decreasing the number of samples along each streamline, or may involve " - "changing the positions of the samples according to some specified trajectory." - - + "Note that because the length of a streamline is calculated based on the sums of " - "distances between adjacent vertices, resampling a streamline to a new set of " - "vertices will typically change the quantified length of that streamline; the " - "magnitude of the difference will typically depend on the discrepancy in the " - "number of vertices, with less vertices leading to a shorter length (due to " - "taking chordal lengths of curved trajectories)."; + +"It is necessary to specify precisely ONE of the command-line options for " + "controlling how this resampling takes place; this may be either increasing " + "or decreasing the number of samples along each streamline, or may involve " + "changing the positions of the samples according to some specified trajectory." + + + "Note that because the length of a streamline is calculated based on the sums of " + "distances between adjacent vertices, resampling a streamline to a new set of " + "vertices will typically change the quantified length of that streamline; the " + "magnitude of the difference will typically depend on the discrepancy in the " + "number of vertices, with less vertices leading to a shorter length (due to " + "taking chordal lengths of curved trajectories)."; ARGUMENTS - + Argument ("in_tracks", "the input track file").type_tracks_in() - + Argument ("out_tracks", "the output resampled tracks").type_tracks_out(); + +Argument("in_tracks", "the input track file").type_tracks_in() + + Argument("out_tracks", "the output resampled tracks").type_tracks_out(); OPTIONS - + Resampling::ResampleOption; + +Resampling::ResampleOption; - // TODO Resample according to an exemplar streamline + // TODO Resample according to an exemplar streamline } - - using value_type = float; +class Worker { +public: + Worker(const std::unique_ptr &in) : resampler(in->clone()) {} + Worker(const Worker &that) : resampler(that.resampler->clone()) {} -class Worker -{ - public: - Worker (const std::unique_ptr& in) : - resampler (in->clone()) { } - - Worker (const Worker& that) : - resampler (that.resampler->clone()) { } + bool operator()(const Streamline &in, Streamline &out) const { + (*resampler)(in, out); + return true; + } - bool operator() (const Streamline& in, Streamline& out) const { - (*resampler) (in, out); - return true; - } - - private: - std::unique_ptr resampler; +private: + std::unique_ptr resampler; }; - - -class Receiver -{ - public: - Receiver (const std::string& path, const Properties& properties) : - writer (path, properties), - progress ("resampling streamlines") { } - - bool operator() (const Streamline& tck) { - auto progress_message = [&](){ return "resampling streamlines (count: " + str(writer.count) + ", skipped: " + str(writer.total_count - writer.count) + ")"; }; - writer (tck); - progress.set_text (progress_message()); - return true; - } - - private: - Writer writer; - ProgressBar progress; - +class Receiver { +public: + Receiver(const std::string &path, const Properties &properties) + : writer(path, properties), progress("resampling streamlines") {} + + bool operator()(const Streamline &tck) { + auto progress_message = [&]() { + return "resampling streamlines (count: " + str(writer.count) + + ", skipped: " + str(writer.total_count - writer.count) + ")"; + }; + writer(tck); + progress.set_text(progress_message()); + return true; + } + +private: + Writer writer; + ProgressBar progress; }; - - -void run () -{ +void run() { Properties properties; - Reader read (argument[0], properties); - - const std::unique_ptr resampler (Resampling::get_resampler()); + Reader read(argument[0], properties); - Worker worker (resampler); - Receiver receiver (argument[1], properties); - Thread::run_ordered_queue (read, - Thread::batch (Streamline()), - Thread::multi (worker), - Thread::batch (Streamline()), - receiver); + const std::unique_ptr resampler(Resampling::get_resampler()); + Worker worker(resampler); + Receiver receiver(argument[1], properties); + Thread::run_ordered_queue(read, + Thread::batch(Streamline()), + Thread::multi(worker), + Thread::batch(Streamline()), + receiver); } diff --git a/cmd/tcksample.cpp b/cmd/tcksample.cpp index aafd12cb00..5ceee29381 100644 --- a/cmd/tcksample.cpp +++ b/cmd/tcksample.cpp @@ -15,71 +15,67 @@ */ #include "command.h" -#include "image.h" -#include "image_helpers.h" -#include "memory.h" -#include "thread.h" -#include "ordered_thread_queue.h" #include "dwi/tractography/file.h" +#include "dwi/tractography/mapping/mapper.h" #include "dwi/tractography/properties.h" #include "dwi/tractography/scalar_file.h" -#include "dwi/tractography/mapping/mapper.h" #include "file/matrix.h" #include "file/ofstream.h" #include "file/path.h" +#include "image.h" +#include "image_helpers.h" #include "interp/linear.h" #include "interp/nearest.h" #include "math/median.h" - - - +#include "memory.h" +#include "ordered_thread_queue.h" +#include "thread.h" using namespace MR; using namespace App; - enum stat_tck { MEAN, MEDIAN, MIN, MAX, NONE }; -const char* statistics[] = { "mean", "median", "min", "max", nullptr }; +const char *statistics[] = {"mean", "median", "min", "max", nullptr}; enum interp_type { NEAREST, LINEAR, PRECISE }; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Sample values of an associated image along tracks"; DESCRIPTION - + "By default, the value of the underlying image at each point along the track " - "is written to either an ASCII file (with all values for each track on the same " - "line), or a track scalar file (.tsf). Alternatively, some statistic can be " - "taken from the values along each streamline and written to a vector file."; + +"By default, the value of the underlying image at each point along the track " + "is written to either an ASCII file (with all values for each track on the same " + "line), or a track scalar file (.tsf). Alternatively, some statistic can be " + "taken from the values along each streamline and written to a vector file."; ARGUMENTS - + Argument ("tracks", "the input track file").type_tracks_in() - + Argument ("image", "the image to be sampled").type_image_in() - + Argument ("values", "the output sampled values").type_file_out(); + +Argument("tracks", "the input track file").type_tracks_in() + + Argument("image", "the image to be sampled").type_image_in() + + Argument("values", "the output sampled values").type_file_out(); OPTIONS - + Option ("stat_tck", "compute some statistic from the values along each streamline " - "(options are: " + join(statistics, ",") + ")") - + Argument ("statistic").type_choice (statistics) - - + Option ("nointerp", "do not use trilinear interpolation when sampling image values") + +Option("stat_tck", + "compute some statistic from the values along each streamline " + "(options are: " + + join(statistics, ",") + ")") + + Argument("statistic").type_choice(statistics) - + Option ("precise", "use the precise mechanism for mapping streamlines to voxels " - "(obviates the need for trilinear interpolation) " - "(only applicable if some per-streamline statistic is requested)") + + Option("nointerp", "do not use trilinear interpolation when sampling image values") - + Option ("use_tdi_fraction", - "each streamline is assigned a fraction of the image intensity " - "in each voxel based on the fraction of the track density " - "contributed by that streamline (this is only appropriate for " - "processing a whole-brain tractogram, and images for which the " - "quantiative parameter is additive)"); + + Option("precise", + "use the precise mechanism for mapping streamlines to voxels " + "(obviates the need for trilinear interpolation) " + "(only applicable if some per-streamline statistic is requested)") + + Option("use_tdi_fraction", + "each streamline is assigned a fraction of the image intensity " + "in each voxel based on the fraction of the track density " + "contributed by that streamline (this is only appropriate for " + "processing a whole-brain tractogram, and images for which the " + "quantiative parameter is additive)"); // TODO add support for SH amplitude along tangent // TODO add support for reading from fixel image @@ -87,434 +83,385 @@ void usage () // (wait until fixel_twi is merged; should simplify) REFERENCES - + "* If using -precise option: " // Internal - "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " - "SIFT: Spherical-deconvolution informed filtering of tractograms. " - "NeuroImage, 2013, 67, 298-312"; - - + +"* If using -precise option: " // Internal + "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " + "SIFT: Spherical-deconvolution informed filtering of tractograms. " + "NeuroImage, 2013, 67, 298-312"; } - - using value_type = float; using vector_type = Eigen::VectorXf; +class TDI { +public: + TDI(Image &image, const size_t num_tracks) + : image(image), progress("Generating initial TDI", num_tracks) {} + ~TDI() { progress.done(); } - -class TDI { - public: - TDI (Image& image, const size_t num_tracks) : - image (image), - progress ("Generating initial TDI", num_tracks) { } - ~TDI () { progress.done(); } - - bool operator() (const DWI::Tractography::Mapping::SetVoxel& in) - { - for (const auto& v : in) { - assign_pos_of (v, 0, 3).to (image); - image.value() += v.get_length(); - } - ++progress; - return true; + bool operator()(const DWI::Tractography::Mapping::SetVoxel &in) { + for (const auto &v : in) { + assign_pos_of(v, 0, 3).to(image); + image.value() += v.get_length(); } + ++progress; + return true; + } - protected: - Image& image; - ProgressBar progress; - +protected: + Image ℑ + ProgressBar progress; }; +template class SamplerNonPrecise { +public: + SamplerNonPrecise(Image &image, const stat_tck statistic, const Image &precalc_tdi) + : interp(image), + mapper(precalc_tdi.valid() ? new DWI::Tractography::Mapping::TrackMapperBase(image) : nullptr), + tdi(precalc_tdi), + statistic(statistic) { + if (mapper) + mapper->set_use_precise_mapping(false); + } + bool operator()(DWI::Tractography::Streamline &tck, std::pair &out) { + assert(statistic != stat_tck::NONE); + out.first = tck.get_index(); -template -class SamplerNonPrecise -{ - public: - SamplerNonPrecise (Image& image, const stat_tck statistic, const Image& precalc_tdi) : - interp (image), - mapper (precalc_tdi.valid() ? new DWI::Tractography::Mapping::TrackMapperBase (image) : nullptr), - tdi (precalc_tdi), - statistic (statistic) - { - if (mapper) - mapper->set_use_precise_mapping (false); - } - - bool operator() (DWI::Tractography::Streamline& tck, std::pair& out) - { - assert (statistic != stat_tck::NONE); - out.first = tck.get_index(); - - DWI::Tractography::TrackScalar values; - (*this) (tck, values); - - if (statistic == MEAN) { - // Take distance between points into account in mean calculation - // (Should help down-weight endpoints) - value_type integral = value_type(0), sum_lengths = value_type(0); - for (size_t i = 0; i != tck.size(); ++i) { - value_type length = value_type(0); - if (i) - length += (tck[i] - tck[i-1]).norm(); - if (i < tck.size() - 1) - length += (tck[i+1] - tck[i]).norm(); - length *= 0.5; - integral += values[i] * length; - sum_lengths += length; - } - out.second = sum_lengths ? (integral / sum_lengths) : 0.0; - } else { - if (statistic == MEDIAN) { - // Don't bother with a weighted median here - vector data; - data.assign (values.data(), values.data() + values.size()); - out.second = Math::median (data); - } else if (statistic == MIN) { - out.second = std::numeric_limits::infinity(); - for (size_t i = 0; i != tck.size(); ++i) - out.second = std::min (out.second, values[i]); - } else if (statistic == MAX) { - out.second = -std::numeric_limits::infinity(); - for (size_t i = 0; i != tck.size(); ++i) - out.second = std::max (out.second, values[i]); - } else { - assert (0); - } - } - - if (!std::isfinite (out.second)) - out.second = NaN; + DWI::Tractography::TrackScalar values; + (*this)(tck, values); - return true; - } - - bool operator() (const DWI::Tractography::Streamline& tck, DWI::Tractography::TrackScalar& out) - { - out.set_index (tck.get_index()); - out.resize (tck.size()); + if (statistic == MEAN) { + // Take distance between points into account in mean calculation + // (Should help down-weight endpoints) + value_type integral = value_type(0), sum_lengths = value_type(0); for (size_t i = 0; i != tck.size(); ++i) { - if (interp.scanner (tck[i])) - out[i] = interp.value(); - else - out[i] = value_type(0); + value_type length = value_type(0); + if (i) + length += (tck[i] - tck[i - 1]).norm(); + if (i < tck.size() - 1) + length += (tck[i + 1] - tck[i]).norm(); + length *= 0.5; + integral += values[i] * length; + sum_lengths += length; } - return true; - } - - private: - Interp interp; - std::shared_ptr mapper; - Image tdi; - const stat_tck statistic; - - value_type get_tdi_multiplier (const DWI::Tractography::Mapping::Voxel& v) - { - if (!tdi.valid()) - return value_type(1); - assign_pos_of (v).to (tdi); - assert (!is_out_of_bounds (tdi)); - return v.get_length() / tdi.value(); - } - -}; - - - -class SamplerPrecise -{ - public: - SamplerPrecise (Image& image, const stat_tck statistic, const Image& precalc_tdi) : - image (image), - mapper (new DWI::Tractography::Mapping::TrackMapperBase (image)), - tdi (precalc_tdi), - statistic (statistic) - { - assert (statistic != stat_tck::NONE); - mapper->set_use_precise_mapping (true); - } - - bool operator() (DWI::Tractography::Streamline& tck, std::pair& out) - { - out.first = tck.get_index(); - value_type sum_lengths = value_type(0); - - DWI::Tractography::Mapping::SetVoxel voxels; - (*mapper) (tck, voxels); - - if (statistic == MEAN) { - value_type integral = value_type(0.0); - for (const auto& v : voxels) { - assign_pos_of (v).to (image); - integral += v.get_length() * (image.value() * get_tdi_multiplier (v)); - sum_lengths += v.get_length(); - } - out.second = integral / sum_lengths; - } else if (statistic == MEDIAN) { - // Should be a weighted median... - // Just use the n.log(n) algorithm - class WeightSort { - public: - WeightSort (const DWI::Tractography::Mapping::Voxel& voxel, const value_type value) : - value (value), - length (voxel.get_length()) { } - bool operator< (const WeightSort& that) const { return value < that.value; } - value_type value, length; - }; - vector data; - for (const auto& v : voxels) { - assign_pos_of (v).to (image); - data.push_back (WeightSort (v, (image.value() * get_tdi_multiplier (v)))); - sum_lengths += v.get_length(); - } - std::sort (data.begin(), data.end()); - const value_type target_length = 0.5 * sum_lengths; - sum_lengths = value_type(0.0); - value_type prev_value = data.front().value; - for (const auto& d : data) { - if ((sum_lengths += d.length) > target_length) { - out.second = prev_value; - break; - } - prev_value = d.value; - } + out.second = sum_lengths ? (integral / sum_lengths) : 0.0; + } else { + if (statistic == MEDIAN) { + // Don't bother with a weighted median here + vector data; + data.assign(values.data(), values.data() + values.size()); + out.second = Math::median(data); } else if (statistic == MIN) { out.second = std::numeric_limits::infinity(); - for (const auto& v : voxels) { - assign_pos_of (v).to (image); - out.second = std::min (out.second, value_type (image.value() * get_tdi_multiplier (v))); - sum_lengths += v.get_length(); - } + for (size_t i = 0; i != tck.size(); ++i) + out.second = std::min(out.second, values[i]); } else if (statistic == MAX) { out.second = -std::numeric_limits::infinity(); - for (const auto& v : voxels) { - assign_pos_of (v).to (image); - out.second = std::max (out.second, value_type (image.value() * get_tdi_multiplier (v))); - sum_lengths += v.get_length(); - } + for (size_t i = 0; i != tck.size(); ++i) + out.second = std::max(out.second, values[i]); } else { - assert (0); + assert(0); } - - if (!std::isfinite (out.second)) - out.second = NaN; - - return true; } + if (!std::isfinite(out.second)) + out.second = NaN; - private: - Image image; - std::shared_ptr mapper; - Image tdi; - const stat_tck statistic; + return true; + } - value_type get_tdi_multiplier (const DWI::Tractography::Mapping::Voxel& v) - { - if (!tdi.valid()) - return value_type(1); - assign_pos_of (v).to (tdi); - assert (!is_out_of_bounds (tdi)); - return v.get_length() / tdi.value(); + bool operator()(const DWI::Tractography::Streamline &tck, + DWI::Tractography::TrackScalar &out) { + out.set_index(tck.get_index()); + out.resize(tck.size()); + for (size_t i = 0; i != tck.size(); ++i) { + if (interp.scanner(tck[i])) + out[i] = interp.value(); + else + out[i] = value_type(0); } + return true; + } +private: + Interp interp; + std::shared_ptr mapper; + Image tdi; + const stat_tck statistic; + + value_type get_tdi_multiplier(const DWI::Tractography::Mapping::Voxel &v) { + if (!tdi.valid()) + return value_type(1); + assign_pos_of(v).to(tdi); + assert(!is_out_of_bounds(tdi)); + return v.get_length() / tdi.value(); + } }; +class SamplerPrecise { +public: + SamplerPrecise(Image &image, const stat_tck statistic, const Image &precalc_tdi) + : image(image), + mapper(new DWI::Tractography::Mapping::TrackMapperBase(image)), + tdi(precalc_tdi), + statistic(statistic) { + assert(statistic != stat_tck::NONE); + mapper->set_use_precise_mapping(true); + } + bool operator()(DWI::Tractography::Streamline &tck, std::pair &out) { + out.first = tck.get_index(); + value_type sum_lengths = value_type(0); -class ReceiverBase { - public: - ReceiverBase (const size_t num_tracks) : - received (0), - expected (num_tracks), - progress ("Sampling values underlying streamlines", num_tracks) { } - - ReceiverBase (const ReceiverBase&) = delete; - - virtual ~ReceiverBase() { - if (received != expected) - WARN ("Track file reports " + str(expected) + " tracks, but contains " + str(received)); - } + DWI::Tractography::Mapping::SetVoxel voxels; + (*mapper)(tck, voxels); - protected: - void operator++ () { - ++received; - ++progress; + if (statistic == MEAN) { + value_type integral = value_type(0.0); + for (const auto &v : voxels) { + assign_pos_of(v).to(image); + integral += v.get_length() * (image.value() * get_tdi_multiplier(v)); + sum_lengths += v.get_length(); + } + out.second = integral / sum_lengths; + } else if (statistic == MEDIAN) { + // Should be a weighted median... + // Just use the n.log(n) algorithm + class WeightSort { + public: + WeightSort(const DWI::Tractography::Mapping::Voxel &voxel, const value_type value) + : value(value), length(voxel.get_length()) {} + bool operator<(const WeightSort &that) const { return value < that.value; } + value_type value, length; + }; + vector data; + for (const auto &v : voxels) { + assign_pos_of(v).to(image); + data.push_back(WeightSort(v, (image.value() * get_tdi_multiplier(v)))); + sum_lengths += v.get_length(); + } + std::sort(data.begin(), data.end()); + const value_type target_length = 0.5 * sum_lengths; + sum_lengths = value_type(0.0); + value_type prev_value = data.front().value; + for (const auto &d : data) { + if ((sum_lengths += d.length) > target_length) { + out.second = prev_value; + break; + } + prev_value = d.value; + } + } else if (statistic == MIN) { + out.second = std::numeric_limits::infinity(); + for (const auto &v : voxels) { + assign_pos_of(v).to(image); + out.second = std::min(out.second, value_type(image.value() * get_tdi_multiplier(v))); + sum_lengths += v.get_length(); + } + } else if (statistic == MAX) { + out.second = -std::numeric_limits::infinity(); + for (const auto &v : voxels) { + assign_pos_of(v).to(image); + out.second = std::max(out.second, value_type(image.value() * get_tdi_multiplier(v))); + sum_lengths += v.get_length(); + } + } else { + assert(0); } - size_t received; + if (!std::isfinite(out.second)) + out.second = NaN; - private: - const size_t expected; - ProgressBar progress; + return true; + } +private: + Image image; + std::shared_ptr mapper; + Image tdi; + const stat_tck statistic; + + value_type get_tdi_multiplier(const DWI::Tractography::Mapping::Voxel &v) { + if (!tdi.valid()) + return value_type(1); + assign_pos_of(v).to(tdi); + assert(!is_out_of_bounds(tdi)); + return v.get_length() / tdi.value(); + } }; +class ReceiverBase { +public: + ReceiverBase(const size_t num_tracks) + : received(0), expected(num_tracks), progress("Sampling values underlying streamlines", num_tracks) {} -class Receiver_Statistic : private ReceiverBase { - public: - Receiver_Statistic (const size_t num_tracks) : - ReceiverBase (num_tracks), - vector_data (vector_type::Zero (num_tracks)) { } - Receiver_Statistic (const Receiver_Statistic&) = delete; + ReceiverBase(const ReceiverBase &) = delete; - bool operator() (std::pair& in) { - if (in.first >= size_t(vector_data.size())) - vector_data.conservativeResizeLike (vector_type::Zero (in.first + 1)); - vector_data[in.first] = in.second; - ++(*this); - return true; - } + virtual ~ReceiverBase() { + if (received != expected) + WARN("Track file reports " + str(expected) + " tracks, but contains " + str(received)); + } - void save (const std::string& path) { - File::Matrix::save_vector (vector_data, path); - } +protected: + void operator++() { + ++received; + ++progress; + } + + size_t received; - private: - vector_type vector_data; +private: + const size_t expected; + ProgressBar progress; }; +class Receiver_Statistic : private ReceiverBase { +public: + Receiver_Statistic(const size_t num_tracks) : ReceiverBase(num_tracks), vector_data(vector_type::Zero(num_tracks)) {} + Receiver_Statistic(const Receiver_Statistic &) = delete; + + bool operator()(std::pair &in) { + if (in.first >= size_t(vector_data.size())) + vector_data.conservativeResizeLike(vector_type::Zero(in.first + 1)); + vector_data[in.first] = in.second; + ++(*this); + return true; + } + void save(const std::string &path) { File::Matrix::save_vector(vector_data, path); } -class Receiver_NoStatistic : private ReceiverBase { - public: - Receiver_NoStatistic (const std::string& path, - const size_t num_tracks, - const DWI::Tractography::Properties& properties) : - ReceiverBase (num_tracks) - { - if (Path::has_suffix (path, ".tsf")) { - tsf.reset (new DWI::Tractography::ScalarWriter (path, properties)); - } else { - ascii.reset (new File::OFStream (path)); - (*ascii) << "# " << App::command_history_string << "\n"; - } +private: + vector_type vector_data; +}; + +class Receiver_NoStatistic : private ReceiverBase { +public: + Receiver_NoStatistic(const std::string &path, + const size_t num_tracks, + const DWI::Tractography::Properties &properties) + : ReceiverBase(num_tracks) { + if (Path::has_suffix(path, ".tsf")) { + tsf.reset(new DWI::Tractography::ScalarWriter(path, properties)); + } else { + ascii.reset(new File::OFStream(path)); + (*ascii) << "# " << App::command_history_string << "\n"; } - Receiver_NoStatistic (const Receiver_NoStatistic&) = delete; - - bool operator() (const DWI::Tractography::TrackScalar& in) - { - // Requires preservation of order - assert (in.get_index() == ReceiverBase::received); - if (ascii) { - if (in.size()) { - auto i = in.begin(); - (*ascii) << *i; - for (++i; i != in.end(); ++i) - (*ascii) << " " << *i; - } - (*ascii) << "\n"; - } else { - (*tsf) (in); + } + Receiver_NoStatistic(const Receiver_NoStatistic &) = delete; + + bool operator()(const DWI::Tractography::TrackScalar &in) { + // Requires preservation of order + assert(in.get_index() == ReceiverBase::received); + if (ascii) { + if (in.size()) { + auto i = in.begin(); + (*ascii) << *i; + for (++i; i != in.end(); ++i) + (*ascii) << " " << *i; } - ++(*this); - return true; + (*ascii) << "\n"; + } else { + (*tsf)(in); } + ++(*this); + return true; + } - private: - std::unique_ptr ascii; - std::unique_ptr> tsf; +private: + std::unique_ptr ascii; + std::unique_ptr> tsf; }; - - - template -void execute_nostat (DWI::Tractography::Reader& reader, - const DWI::Tractography::Properties& properties, - const size_t num_tracks, - Image& image, - const std::string& path) -{ - SamplerNonPrecise sampler (image, stat_tck::NONE, Image()); - Receiver_NoStatistic receiver (path, num_tracks, properties); - Thread::run_ordered_queue (reader, - Thread::batch (DWI::Tractography::Streamline()), - Thread::multi (sampler), - Thread::batch (DWI::Tractography::TrackScalar()), - receiver); +void execute_nostat(DWI::Tractography::Reader &reader, + const DWI::Tractography::Properties &properties, + const size_t num_tracks, + Image &image, + const std::string &path) { + SamplerNonPrecise sampler(image, stat_tck::NONE, Image()); + Receiver_NoStatistic receiver(path, num_tracks, properties); + Thread::run_ordered_queue(reader, + Thread::batch(DWI::Tractography::Streamline()), + Thread::multi(sampler), + Thread::batch(DWI::Tractography::TrackScalar()), + receiver); } template -void execute (DWI::Tractography::Reader& reader, - const size_t num_tracks, - Image& image, - const stat_tck statistic, - Image& tdi, - const std::string& path) -{ - SamplerType sampler (image, statistic, tdi); - Receiver_Statistic receiver (num_tracks); - Thread::run_ordered_queue (reader, - Thread::batch (DWI::Tractography::Streamline()), - Thread::multi (sampler), - Thread::batch (std::pair()), - receiver); - receiver.save (path); +void execute(DWI::Tractography::Reader &reader, + const size_t num_tracks, + Image &image, + const stat_tck statistic, + Image &tdi, + const std::string &path) { + SamplerType sampler(image, statistic, tdi); + Receiver_Statistic receiver(num_tracks); + Thread::run_ordered_queue(reader, + Thread::batch(DWI::Tractography::Streamline()), + Thread::multi(sampler), + Thread::batch(std::pair()), + receiver); + receiver.save(path); } - - -void run () -{ +void run() { DWI::Tractography::Properties properties; - DWI::Tractography::Reader reader (argument[0], properties); - auto H = Header::open (argument[1]); + DWI::Tractography::Reader reader(argument[0], properties); + auto H = Header::open(argument[1]); auto image = H.get_image(); - auto opt = get_options ("stat_tck"); + auto opt = get_options("stat_tck"); const stat_tck statistic = opt.size() ? stat_tck(int(opt[0][0])) : stat_tck::NONE; - const bool nointerp = get_options ("nointerp").size(); - const bool precise = get_options ("precise").size(); + const bool nointerp = get_options("nointerp").size(); + const bool precise = get_options("precise").size(); if (nointerp && precise) - throw Exception ("Option -nointerp and -precise are mutually exclusive"); + throw Exception("Option -nointerp and -precise are mutually exclusive"); const interp_type interp = nointerp ? interp_type::NEAREST : (precise ? interp_type::PRECISE : interp_type::LINEAR); - const size_t num_tracks = properties.find("count") == properties.end() ? - 0 : - to(properties["count"]); + const size_t num_tracks = properties.find("count") == properties.end() ? 0 : to(properties["count"]); if (statistic == stat_tck::NONE && interp == interp_type::PRECISE) - throw Exception ("Precise streamline mapping may only be used with per-streamline statistics"); + throw Exception("Precise streamline mapping may only be used with per-streamline statistics"); Image tdi; - if (get_options ("use_tdi_fraction").size()) { + if (get_options("use_tdi_fraction").size()) { if (statistic == stat_tck::NONE) - throw Exception ("Cannot use -use_tdi_fraction option unless a per-streamline statistic is used"); - DWI::Tractography::Reader tdi_reader (argument[0], properties); - DWI::Tractography::Mapping::TrackMapperBase mapper (H); - mapper.set_use_precise_mapping (interp == interp_type::PRECISE); - tdi = Image::scratch (H, "TDI scratch image"); - TDI tdi_fill (tdi, num_tracks); - Thread::run_queue (tdi_reader, - Thread::batch (DWI::Tractography::Streamline()), - Thread::multi (mapper), - Thread::batch (DWI::Tractography::Mapping::SetVoxel()), - tdi_fill); + throw Exception("Cannot use -use_tdi_fraction option unless a per-streamline statistic is used"); + DWI::Tractography::Reader tdi_reader(argument[0], properties); + DWI::Tractography::Mapping::TrackMapperBase mapper(H); + mapper.set_use_precise_mapping(interp == interp_type::PRECISE); + tdi = Image::scratch(H, "TDI scratch image"); + TDI tdi_fill(tdi, num_tracks); + Thread::run_queue(tdi_reader, + Thread::batch(DWI::Tractography::Streamline()), + Thread::multi(mapper), + Thread::batch(DWI::Tractography::Mapping::SetVoxel()), + tdi_fill); } if (statistic == stat_tck::NONE) { switch (interp) { - case interp_type::NEAREST: - execute_nostat>> (reader, properties, num_tracks, image, argument[2]); - break; - case interp_type::LINEAR: - execute_nostat>> (reader, properties, num_tracks, image, argument[2]); - break; - case interp_type::PRECISE: - throw Exception ("Precise streamline mapping may only be used with per-streamline statistics"); + case interp_type::NEAREST: + execute_nostat>>(reader, properties, num_tracks, image, argument[2]); + break; + case interp_type::LINEAR: + execute_nostat>>(reader, properties, num_tracks, image, argument[2]); + break; + case interp_type::PRECISE: + throw Exception("Precise streamline mapping may only be used with per-streamline statistics"); } } else { switch (interp) { - case interp_type::NEAREST: - execute>>> (reader, num_tracks, image, statistic, tdi, argument[2]); - break; - case interp_type::LINEAR: - execute>>> (reader, num_tracks, image, statistic, tdi, argument[2]); - break; - case interp_type::PRECISE: - execute (reader, num_tracks, image, statistic, tdi, argument[2]); - break; + case interp_type::NEAREST: + execute>>>( + reader, num_tracks, image, statistic, tdi, argument[2]); + break; + case interp_type::LINEAR: + execute>>>( + reader, num_tracks, image, statistic, tdi, argument[2]); + break; + case interp_type::PRECISE: + execute(reader, num_tracks, image, statistic, tdi, argument[2]); + break; } } } - diff --git a/cmd/tcksift.cpp b/cmd/tcksift.cpp index b043e8b46c..40c8cf9886 100644 --- a/cmd/tcksift.cpp +++ b/cmd/tcksift.cpp @@ -26,9 +26,6 @@ #include "dwi/tractography/SIFT/sift.h" #include "dwi/tractography/SIFT/sifter.h" - - - using namespace MR; using namespace App; @@ -37,116 +34,105 @@ using namespace MR::DWI::Tractography; using namespace MR::DWI::Tractography::Mapping; using namespace MR::DWI::Tractography::SIFT; - - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; - SYNOPSIS = "Filter a whole-brain fibre-tracking data set such that the streamline densities match the FOD lobe integrals"; + SYNOPSIS = + "Filter a whole-brain fibre-tracking data set such that the streamline densities match the FOD lobe integrals"; REFERENCES - + "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal - "SIFT: Spherical-deconvolution informed filtering of tractograms. " - "NeuroImage, 2013, 67, 298-312"; + +"Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal + "SIFT: Spherical-deconvolution informed filtering of tractograms. " + "NeuroImage, 2013, 67, 298-312"; ARGUMENTS - + Argument ("in_tracks", "the input track file").type_tracks_in() - + Argument ("in_fod", "input image containing the spherical harmonics of the fibre orientation distributions").type_image_in() - + Argument ("out_tracks", "the output filtered tracks file").type_tracks_out(); + +Argument("in_tracks", "the input track file").type_tracks_in() + + Argument("in_fod", "input image containing the spherical harmonics of the fibre orientation distributions") + .type_image_in() + + Argument("out_tracks", "the output filtered tracks file").type_tracks_out(); OPTIONS - + Option ("nofilter", "do NOT perform track filtering - just construct the model in order to provide output debugging images") + +Option("nofilter", + "do NOT perform track filtering - just construct the model in order to provide output debugging images") - + Option ("output_at_counts", "output filtered track files (and optionally debugging images if -output_debug is specified) at specific " - "numbers of remaining streamlines; provide as comma-separated list of integers") - + Argument ("counts").type_sequence_int() + + + Option("output_at_counts", + "output filtered track files (and optionally debugging images if -output_debug is specified) at specific " + "numbers of remaining streamlines; provide as comma-separated list of integers") + + Argument("counts").type_sequence_int() - + SIFTModelProcMaskOption - + SIFTModelOption - + SIFTOutputOption + + SIFTModelProcMaskOption + SIFTModelOption + SIFTOutputOption - + Option ("out_selection", "output a text file containing the binary selection of streamlines") - + Argument ("path").type_file_out() - - + SIFTTermOption; + + Option("out_selection", "output a text file containing the binary selection of streamlines") + + Argument("path").type_file_out() + + SIFTTermOption; } +void run() { + const std::string debug_path = get_option_value("output_debug", ""); -void run () -{ - - const std::string debug_path = get_option_value ("output_debug", ""); + auto in_dwi = Image::open(argument[1]); + Math::SH::check(in_dwi); + DWI::Directions::FastLookupSet dirs(1281); - auto in_dwi = Image::open (argument[1]); - Math::SH::check (in_dwi); - DWI::Directions::FastLookupSet dirs (1281); - - SIFTer sifter (in_dwi, dirs); + SIFTer sifter(in_dwi, dirs); if (debug_path.size()) { - sifter.initialise_debug_image_output (debug_path); - sifter.output_proc_mask (Path::join (debug_path, "proc_mask.mif")); + sifter.initialise_debug_image_output(debug_path); + sifter.output_proc_mask(Path::join(debug_path, "proc_mask.mif")); if (get_options("act").size()) - sifter.output_5tt_image (Path::join (debug_path, "5tt.mif")); + sifter.output_5tt_image(Path::join(debug_path, "5tt.mif")); } - sifter.perform_FOD_segmentation (in_dwi); + sifter.perform_FOD_segmentation(in_dwi); sifter.scale_FDs_by_GM(); - sifter.map_streamlines (argument[0]); + sifter.map_streamlines(argument[0]); if (debug_path.size()) - sifter.output_all_debug_images (debug_path, "before"); + sifter.output_all_debug_images(debug_path, "before"); - sifter.remove_excluded_fixels (); + sifter.remove_excluded_fixels(); - if (!get_options ("nofilter").size()) { + if (!get_options("nofilter").size()) { - auto opt = get_options ("term_number"); + auto opt = get_options("term_number"); if (opt.size()) - sifter.set_term_number (int(opt[0][0])); - opt = get_options ("term_ratio"); + sifter.set_term_number(int(opt[0][0])); + opt = get_options("term_ratio"); if (opt.size()) - sifter.set_term_ratio (float(opt[0][0])); - opt = get_options ("term_mu"); + sifter.set_term_ratio(float(opt[0][0])); + opt = get_options("term_mu"); if (opt.size()) - sifter.set_term_mu (float(opt[0][0])); - opt = get_options ("csv"); + sifter.set_term_mu(float(opt[0][0])); + opt = get_options("csv"); if (opt.size()) - sifter.set_csv_path (opt[0][0]); - opt = get_options ("output_at_counts"); + sifter.set_csv_path(opt[0][0]); + opt = get_options("output_at_counts"); if (opt.size()) { - vector counts = parse_ints (opt[0][0]); - sifter.set_regular_outputs (counts, debug_path); + vector counts = parse_ints(opt[0][0]); + sifter.set_regular_outputs(counts, debug_path); } sifter.perform_filtering(); if (debug_path.size()) - sifter.output_all_debug_images (debug_path, "after"); + sifter.output_all_debug_images(debug_path, "after"); - sifter.output_filtered_tracks (argument[0], argument[2]); + sifter.output_filtered_tracks(argument[0], argument[2]); - opt = get_options ("out_selection"); + opt = get_options("out_selection"); if (opt.size()) - sifter.output_selection (opt[0][0]); - + sifter.output_selection(opt[0][0]); } - auto opt = get_options ("out_mu"); + auto opt = get_options("out_mu"); if (opt.size()) { - File::OFStream out_mu (opt[0][0]); + File::OFStream out_mu(opt[0][0]); out_mu << sifter.mu(); } - } - - - - diff --git a/cmd/tcksift2.cpp b/cmd/tcksift2.cpp index bb91434dae..a8746e71e4 100644 --- a/cmd/tcksift2.cpp +++ b/cmd/tcksift2.cpp @@ -16,8 +16,8 @@ #include "command.h" #include "exception.h" -#include "image.h" #include "header.h" +#include "image.h" #include "file/path.h" @@ -30,10 +30,6 @@ #include "dwi/tractography/SIFT2/tckfactor.h" - - - - using namespace MR; using namespace App; @@ -41,208 +37,214 @@ using namespace MR::DWI; using namespace MR::DWI::Tractography; using namespace MR::DWI::Tractography::SIFT2; - - - -const OptionGroup SIFT2RegularisationOption = OptionGroup ("Regularisation options for SIFT2") - - + Option ("reg_tikhonov", "provide coefficient for regularising streamline weighting coefficients (Tikhonov regularisation) (default: " + str(SIFT2_REGULARISATION_TIKHONOV_DEFAULT, 2) + ")") - + Argument ("value").type_float (0.0) - - + Option ("reg_tv", "provide coefficient for regularising variance of streamline weighting coefficient to fixels along its length (Total Variation regularisation) (default: " + str(SIFT2_REGULARISATION_TV_DEFAULT, 2) + ")") - + Argument ("value").type_float (0.0); - - - -const OptionGroup SIFT2AlgorithmOption = OptionGroup ("Options for controlling the SIFT2 optimisation algorithm") - - + Option ("min_td_frac", "minimum fraction of the FOD integral reconstructed by streamlines; " - "if the reconstructed streamline density is below this fraction, the fixel is excluded from optimisation " - "(default: " + str(SIFT2_MIN_TD_FRAC_DEFAULT, 2) + ")") - + Argument ("fraction").type_float (0.0, 1.0) - - + Option ("min_iters", "minimum number of iterations to run before testing for convergence; " - "this can prevent premature termination at early iterations if the cost function increases slightly " - "(default: " + str(SIFT2_MIN_ITERS_DEFAULT) + ")") - + Argument ("count").type_integer (0) - - + Option ("max_iters", "maximum number of iterations to run before terminating program") - + Argument ("count").type_integer (0) - - + Option ("min_factor", "minimum weighting factor for an individual streamline; " - "if the factor falls below this number the streamline will be rejected entirely (factor set to zero) " - "(default: " + str(std::exp (SIFT2_MIN_COEFF_DEFAULT), 2) + ")") - + Argument ("factor").type_float (0.0, 1.0) - - + Option ("min_coeff", "minimum weighting coefficient for an individual streamline; " - "similar to the '-min_factor' option, but using the exponential coefficient basis of the SIFT2 model; " - "these parameters are related as: factor = e^(coeff). " - "Note that the -min_factor and -min_coeff options are mutually exclusive - you can only provide one. " - "(default: " + str(SIFT2_MIN_COEFF_DEFAULT, 2) + ")") - + Argument ("coeff").type_float (-std::numeric_limits::infinity(), 0.0) - - + Option ("max_factor", "maximum weighting factor that can be assigned to any one streamline " - "(default: " + str(std::exp (SIFT2_MAX_COEFF_DEFAULT), 2) + ")") - + Argument ("factor").type_float (1.0) - - + Option ("max_coeff", "maximum weighting coefficient for an individual streamline; " - "similar to the '-max_factor' option, but using the exponential coefficient basis of the SIFT2 model; " - "these parameters are related as: factor = e^(coeff). " - "Note that the -max_factor and -max_coeff options are mutually exclusive - you can only provide one. " - "(default: " + str(SIFT2_MAX_COEFF_DEFAULT, 2) + ")") - + Argument ("coeff").type_float (1.0) - - - + Option ("max_coeff_step", "maximum change to a streamline's weighting coefficient in a single iteration " - "(default: " + str(SIFT2_MAX_COEFF_STEP_DEFAULT, 2) + ")") - + Argument ("step").type_float () - - + Option ("min_cf_decrease", "minimum decrease in the cost function (as a fraction of the initial value) that must occur each iteration for the algorithm to continue " - "(default: " + str(SIFT2_MIN_CF_DECREASE_DEFAULT, 2) + ")") - + Argument ("frac").type_float (0.0, 1.0) - - + Option ("linear", "perform a linear estimation of streamline weights, rather than the standard non-linear optimisation " - "(typically does not provide as accurate a model fit; but only requires a single pass)"); - - - - - -void usage () -{ +const OptionGroup SIFT2RegularisationOption = + OptionGroup("Regularisation options for SIFT2") + + + + Option( + "reg_tikhonov", + "provide coefficient for regularising streamline weighting coefficients (Tikhonov regularisation) (default: " + + str(SIFT2_REGULARISATION_TIKHONOV_DEFAULT, 2) + ")") + + Argument("value").type_float(0.0) + + + Option("reg_tv", + "provide coefficient for regularising variance of streamline weighting coefficient to fixels along its " + "length (Total Variation regularisation) (default: " + + str(SIFT2_REGULARISATION_TV_DEFAULT, 2) + ")") + + Argument("value").type_float(0.0); + +const OptionGroup SIFT2AlgorithmOption = + OptionGroup("Options for controlling the SIFT2 optimisation algorithm") + + + Option("min_td_frac", + "minimum fraction of the FOD integral reconstructed by streamlines; " + "if the reconstructed streamline density is below this fraction, the fixel is excluded from optimisation " + "(default: " + + str(SIFT2_MIN_TD_FRAC_DEFAULT, 2) + ")") + + Argument("fraction").type_float(0.0, 1.0) + + + Option("min_iters", + "minimum number of iterations to run before testing for convergence; " + "this can prevent premature termination at early iterations if the cost function increases slightly " + "(default: " + + str(SIFT2_MIN_ITERS_DEFAULT) + ")") + + Argument("count").type_integer(0) + + + Option("max_iters", "maximum number of iterations to run before terminating program") + + Argument("count").type_integer(0) + + + Option("min_factor", + "minimum weighting factor for an individual streamline; " + "if the factor falls below this number the streamline will be rejected entirely (factor set to zero) " + "(default: " + + str(std::exp(SIFT2_MIN_COEFF_DEFAULT), 2) + ")") + + Argument("factor").type_float(0.0, 1.0) + + + Option("min_coeff", + "minimum weighting coefficient for an individual streamline; " + "similar to the '-min_factor' option, but using the exponential coefficient basis of the SIFT2 model; " + "these parameters are related as: factor = e^(coeff). " + "Note that the -min_factor and -min_coeff options are mutually exclusive - you can only provide one. " + "(default: " + + str(SIFT2_MIN_COEFF_DEFAULT, 2) + ")") + + Argument("coeff").type_float(-std::numeric_limits::infinity(), 0.0) + + + Option("max_factor", + "maximum weighting factor that can be assigned to any one streamline " + "(default: " + + str(std::exp(SIFT2_MAX_COEFF_DEFAULT), 2) + ")") + + Argument("factor").type_float(1.0) + + + Option("max_coeff", + "maximum weighting coefficient for an individual streamline; " + "similar to the '-max_factor' option, but using the exponential coefficient basis of the SIFT2 model; " + "these parameters are related as: factor = e^(coeff). " + "Note that the -max_factor and -max_coeff options are mutually exclusive - you can only provide one. " + "(default: " + + str(SIFT2_MAX_COEFF_DEFAULT, 2) + ")") + + Argument("coeff").type_float(1.0) + + + Option("max_coeff_step", + "maximum change to a streamline's weighting coefficient in a single iteration " + "(default: " + + str(SIFT2_MAX_COEFF_STEP_DEFAULT, 2) + ")") + + Argument("step").type_float() + + + Option("min_cf_decrease", + "minimum decrease in the cost function (as a fraction of the initial value) that must occur each " + "iteration for the algorithm to continue " + "(default: " + + str(SIFT2_MIN_CF_DECREASE_DEFAULT, 2) + ")") + + Argument("frac").type_float(0.0, 1.0) + + + Option("linear", + "perform a linear estimation of streamline weights, rather than the standard non-linear optimisation " + "(typically does not provide as accurate a model fit; but only requires a single pass)"); + +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; - SYNOPSIS = "Optimise per-streamline cross-section multipliers to match a whole-brain tractogram to fixel-wise fibre densities"; + SYNOPSIS = "Optimise per-streamline cross-section multipliers to match a whole-brain tractogram to fixel-wise fibre " + "densities"; REFERENCES - + "Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal - "SIFT2: Enabling dense quantitative assessment of brain white matter connectivity using streamlines tractography. " - "NeuroImage, 2015, 119, 338-351" + +"Smith, R. E.; Tournier, J.-D.; Calamante, F. & Connelly, A. " // Internal + "SIFT2: Enabling dense quantitative assessment of brain white matter connectivity using streamlines tractography. " + "NeuroImage, 2015, 119, 338-351" - + "* If using the -linear option: \n" - "Smith, RE; Raffelt, D; Tournier, J-D; Connelly, A. " // Internal - "Quantitative Streamlines Tractography: Methods and Inter-Subject Normalisation. " - "Open Science Framework, https://doi.org/10.31219/osf.io/c67kn."; + + "* If using the -linear option: \n" + "Smith, RE; Raffelt, D; Tournier, J-D; Connelly, A. " // Internal + "Quantitative Streamlines Tractography: Methods and Inter-Subject Normalisation. " + "Open Science Framework, https://doi.org/10.31219/osf.io/c67kn."; ARGUMENTS - + Argument ("in_tracks", "the input track file").type_tracks_in() - + Argument ("in_fod", "input image containing the spherical harmonics of the fibre orientation distributions").type_image_in() - + Argument ("out_weights", "output text file containing the weighting factor for each streamline").type_file_out(); + +Argument("in_tracks", "the input track file").type_tracks_in() + + Argument("in_fod", "input image containing the spherical harmonics of the fibre orientation distributions") + .type_image_in() + + Argument("out_weights", "output text file containing the weighting factor for each streamline").type_file_out(); OPTIONS - + SIFT::SIFTModelProcMaskOption - + SIFT::SIFTModelOption - + SIFT::SIFTOutputOption - - + Option ("out_coeffs", "output text file containing the weighting coefficient for each streamline") - + Argument ("path").type_file_out() + +SIFT::SIFTModelProcMaskOption + SIFT::SIFTModelOption + SIFT::SIFTOutputOption - + SIFT2RegularisationOption - + SIFT2AlgorithmOption; + + Option("out_coeffs", "output text file containing the weighting coefficient for each streamline") + + Argument("path").type_file_out() + + SIFT2RegularisationOption + SIFT2AlgorithmOption; } - - - -void run () -{ +void run() { if (get_options("min_factor").size() && get_options("min_coeff").size()) - throw Exception ("Options -min_factor and -min_coeff are mutually exclusive"); + throw Exception("Options -min_factor and -min_coeff are mutually exclusive"); if (get_options("max_factor").size() && get_options("max_coeff").size()) - throw Exception ("Options -max_factor and -max_coeff are mutually exclusive"); + throw Exception("Options -max_factor and -max_coeff are mutually exclusive"); - if (Path::has_suffix (argument[2], ".tck")) - throw Exception ("Output of tcksift2 command should be a text file, not a tracks file"); + if (Path::has_suffix(argument[2], ".tck")) + throw Exception("Output of tcksift2 command should be a text file, not a tracks file"); - auto in_dwi = Image::open (argument[1]); + auto in_dwi = Image::open(argument[1]); - DWI::Directions::FastLookupSet dirs (1281); + DWI::Directions::FastLookupSet dirs(1281); - TckFactor tckfactor (in_dwi, dirs); + TckFactor tckfactor(in_dwi, dirs); - tckfactor.perform_FOD_segmentation (in_dwi); + tckfactor.perform_FOD_segmentation(in_dwi); tckfactor.scale_FDs_by_GM(); - std::string debug_path = get_option_value ("output_debug", ""); + std::string debug_path = get_option_value("output_debug", ""); if (debug_path.size()) { - tckfactor.initialise_debug_image_output (debug_path); - tckfactor.output_proc_mask (Path::join(debug_path, "proc_mask.mif")); + tckfactor.initialise_debug_image_output(debug_path); + tckfactor.output_proc_mask(Path::join(debug_path, "proc_mask.mif")); } - tckfactor.map_streamlines (argument[0]); + tckfactor.map_streamlines(argument[0]); tckfactor.store_orig_TDs(); - tckfactor.remove_excluded_fixels (get_option_value ("min_td_frac", SIFT2_MIN_TD_FRAC_DEFAULT)); + tckfactor.remove_excluded_fixels(get_option_value("min_td_frac", SIFT2_MIN_TD_FRAC_DEFAULT)); if (debug_path.size()) { - tckfactor.output_TD_images (debug_path, "origTD_fixel.mif", "trackcount_fixel.mif"); - tckfactor.output_all_debug_images (debug_path, "before"); + tckfactor.output_TD_images(debug_path, "origTD_fixel.mif", "trackcount_fixel.mif"); + tckfactor.output_all_debug_images(debug_path, "before"); } - if (get_options ("linear").size()) { + if (get_options("linear").size()) { tckfactor.calc_afcsa(); } else { - auto opt = get_options ("csv"); + auto opt = get_options("csv"); if (opt.size()) - tckfactor.set_csv_path (opt[0][0]); + tckfactor.set_csv_path(opt[0][0]); - const float reg_tikhonov = get_option_value ("reg_tikhonov", SIFT2_REGULARISATION_TIKHONOV_DEFAULT); - const float reg_tv = get_option_value ("reg_tv", SIFT2_REGULARISATION_TV_DEFAULT); - tckfactor.set_reg_lambdas (reg_tikhonov, reg_tv); + const float reg_tikhonov = get_option_value("reg_tikhonov", SIFT2_REGULARISATION_TIKHONOV_DEFAULT); + const float reg_tv = get_option_value("reg_tv", SIFT2_REGULARISATION_TV_DEFAULT); + tckfactor.set_reg_lambdas(reg_tikhonov, reg_tv); - opt = get_options ("min_iters"); + opt = get_options("min_iters"); if (opt.size()) - tckfactor.set_min_iters (int(opt[0][0])); - opt = get_options ("max_iters"); + tckfactor.set_min_iters(int(opt[0][0])); + opt = get_options("max_iters"); if (opt.size()) - tckfactor.set_max_iters (int(opt[0][0])); - opt = get_options ("min_factor"); + tckfactor.set_max_iters(int(opt[0][0])); + opt = get_options("min_factor"); if (opt.size()) - tckfactor.set_min_factor (float(opt[0][0])); - opt = get_options ("min_coeff"); + tckfactor.set_min_factor(float(opt[0][0])); + opt = get_options("min_coeff"); if (opt.size()) - tckfactor.set_min_coeff (float(opt[0][0])); - opt = get_options ("max_factor"); + tckfactor.set_min_coeff(float(opt[0][0])); + opt = get_options("max_factor"); if (opt.size()) - tckfactor.set_max_factor (float(opt[0][0])); - opt = get_options ("max_coeff"); + tckfactor.set_max_factor(float(opt[0][0])); + opt = get_options("max_coeff"); if (opt.size()) - tckfactor.set_max_coeff (float(opt[0][0])); - opt = get_options ("max_coeff_step"); + tckfactor.set_max_coeff(float(opt[0][0])); + opt = get_options("max_coeff_step"); if (opt.size()) - tckfactor.set_max_coeff_step (float(opt[0][0])); - opt = get_options ("min_cf_decrease"); + tckfactor.set_max_coeff_step(float(opt[0][0])); + opt = get_options("min_cf_decrease"); if (opt.size()) - tckfactor.set_min_cf_decrease (float(opt[0][0])); + tckfactor.set_min_cf_decrease(float(opt[0][0])); tckfactor.estimate_factors(); - } tckfactor.report_entropy(); - tckfactor.output_factors (argument[2]); + tckfactor.output_factors(argument[2]); - auto opt = get_options ("out_coeffs"); + auto opt = get_options("out_coeffs"); if (opt.size()) - tckfactor.output_coefficients (opt[0][0]); + tckfactor.output_coefficients(opt[0][0]); if (debug_path.size()) - tckfactor.output_all_debug_images (debug_path, "after"); + tckfactor.output_all_debug_images(debug_path, "after"); - opt = get_options ("out_mu"); + opt = get_options("out_mu"); if (opt.size()) { - File::OFStream out_mu (opt[0][0]); + File::OFStream out_mu(opt[0][0]); out_mu << tckfactor.mu(); } - } - - diff --git a/cmd/tckstats.cpp b/cmd/tckstats.cpp index f72846c85f..6584389fef 100644 --- a/cmd/tckstats.cpp +++ b/cmd/tckstats.cpp @@ -27,82 +27,68 @@ #include "dwi/tractography/properties.h" #include "dwi/tractography/weights.h" - - - using namespace MR; using namespace App; using namespace MR::DWI; using namespace MR::DWI::Tractography; - // TODO Make compatible with stats generic options? // - Some features would not be compatible due to potential presence of track weights +const char *field_choices[] = {"mean", "median", "std", "min", "max", "count", NULL}; -const char * field_choices[] = { "mean", "median", "std", "min", "max", "count", NULL }; - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Calculate statistics on streamlines lengths"; ARGUMENTS - + Argument ("tracks_in", "the input track file").type_tracks_in(); + +Argument("tracks_in", "the input track file").type_tracks_in(); OPTIONS - + Option ("output", - "output only the field specified. Multiple such options can be supplied if required. " - "Choices are: " + join (field_choices, ", ") + ". Useful for use in scripts.").allow_multiple() - + Argument ("field").type_choice (field_choices) - - + Option ("histogram", "output a histogram of streamline lengths") - + Argument ("path").type_file_out() + +Option("output", + "output only the field specified. Multiple such options can be supplied if required. " + "Choices are: " + + join(field_choices, ", ") + ". Useful for use in scripts.") + .allow_multiple() + + Argument("field").type_choice(field_choices) - + Option ("dump", "dump the streamlines lengths to a text file") - + Argument ("path").type_file_out() + + Option("histogram", "output a histogram of streamline lengths") + Argument("path").type_file_out() - + Option ("ignorezero", "do not generate a warning if the track file contains streamlines with zero length") + + Option("dump", "dump the streamlines lengths to a text file") + Argument("path").type_file_out() - + Tractography::TrackWeightsInOption; + + Option("ignorezero", "do not generate a warning if the track file contains streamlines with zero length") + + Tractography::TrackWeightsInOption; } - // Store length and weight of each streamline -class LW { - public: - LW (const float l, const float w) : length (l), weight (w) { } - LW () : length (NaN), weight (NaN) { } - bool operator< (const LW& that) const { return length < that.length; } - float get_length() const { return length; } - float get_weight() const { return weight; } - private: - float length, weight; - friend LW operator+ (const LW&, const LW&); - friend LW operator/ (const LW&, const double); +class LW { +public: + LW(const float l, const float w) : length(l), weight(w) {} + LW() : length(NaN), weight(NaN) {} + bool operator<(const LW &that) const { return length < that.length; } + float get_length() const { return length; } + float get_weight() const { return weight; } + +private: + float length, weight; + friend LW operator+(const LW &, const LW &); + friend LW operator/(const LW &, const double); }; // Necessary for median template function -LW operator+ (const LW& one, const LW& two) -{ - return LW (one.get_length() + two.get_length(), one.get_weight() + two.get_weight()); -} - -LW operator/ (const LW& lw, const double div) -{ - return LW (lw.get_length() / div, lw.get_weight() / div); +LW operator+(const LW &one, const LW &two) { + return LW(one.get_length() + two.get_length(), one.get_weight() + two.get_weight()); } +LW operator/(const LW &lw, const double div) { return LW(lw.get_length() / div, lw.get_weight() / div); } -void run () -{ +void run() { - const bool weights_provided = get_options ("tck_weights_in").size(); + const bool weights_provided = get_options("tck_weights_in").size(); float step_size = NaN; size_t count = 0, header_count = 0; @@ -112,54 +98,55 @@ void run () default_type sum_lengths = 0.0, sum_weights = 0.0; vector histogram; vector all_lengths; - all_lengths.reserve (header_count); + all_lengths.reserve(header_count); { Tractography::Properties properties; - Tractography::Reader reader (argument[0], properties); + Tractography::Reader reader(argument[0], properties); - if (properties.find ("count") != properties.end()) - header_count = to (properties["count"]); + if (properties.find("count") != properties.end()) + header_count = to(properties["count"]); step_size = properties.get_stepsize(); - if ((!std::isfinite (step_size) || !step_size) && get_options ("histogram").size()) { - WARN ("Do not have streamline step size with which to bin histogram; histogram will be generated using 1mm bin widths"); + if ((!std::isfinite(step_size) || !step_size) && get_options("histogram").size()) { + WARN("Do not have streamline step size with which to bin histogram; histogram will be generated using 1mm bin " + "widths"); } vector dump; - dump.reserve (header_count); + dump.reserve(header_count); - ProgressBar progress ("Reading track file", header_count); + ProgressBar progress("Reading track file", header_count); Streamline<> tck; - while (reader (tck)) { + while (reader(tck)) { ++count; - const float length = Tractography::length (tck); - if (std::isfinite (length)) { - min_length = std::min (min_length, length); - max_length = std::max (max_length, length); + const float length = Tractography::length(tck); + if (std::isfinite(length)) { + min_length = std::min(min_length, length); + max_length = std::max(max_length, length); sum_lengths += tck.weight * length; sum_weights += tck.weight; - all_lengths.push_back (LW (length, tck.weight)); - const size_t index = std::isfinite (step_size) ? std::round (length / step_size) : std::round (length); + all_lengths.push_back(LW(length, tck.weight)); + const size_t index = std::isfinite(step_size) ? std::round(length / step_size) : std::round(length); while (histogram.size() <= index) - histogram.push_back (0.0); + histogram.push_back(0.0); histogram[index] += tck.weight; if (!length) ++zero_length_streamlines; } else { ++empty_streamlines; } - dump.push_back (length); + dump.push_back(length); ++progress; } - auto opt = get_options ("dump"); + auto opt = get_options("dump"); if (opt.size()) - File::Matrix::save_vector (dump, opt[0][0]); + File::Matrix::save_vector(dump, opt[0][0]); } - if (!get_options ("ignorezero").size() && (empty_streamlines || zero_length_streamlines)) { - std::string s ("read"); + if (!get_options("ignorezero").size() && (empty_streamlines || zero_length_streamlines)) { + std::string s("read"); if (empty_streamlines) { s += " " + str(empty_streamlines) + " empty streamlines"; if (zero_length_streamlines) @@ -167,13 +154,13 @@ void run () } if (zero_length_streamlines) s += " " + str(zero_length_streamlines) + " streamlines with zero length (one vertex only)"; - WARN (s); + WARN(s); } if (count != header_count) - WARN ("expected " + str(header_count) + " tracks according to header; read " + str(count)); - if (!std::isfinite (min_length)) + WARN("expected " + str(header_count) + " tracks according to header; read " + str(count)); + if (!std::isfinite(min_length)) min_length = NaN; - if (!std::isfinite (max_length)) + if (!std::isfinite(max_length)) max_length = NaN; const float mean_length = sum_weights ? (sum_lengths / sum_weights) : NaN; @@ -182,13 +169,15 @@ void run () if (count) { if (weights_provided) { // Perform a weighted median calculation - std::sort (all_lengths.begin(), all_lengths.end()); + std::sort(all_lengths.begin(), all_lengths.end()); size_t median_index = 0; default_type sum = sum_weights - all_lengths[0].get_weight(); - while (sum > 0.5 * sum_weights) { sum -= all_lengths[++median_index].get_weight(); } + while (sum > 0.5 * sum_weights) { + sum -= all_lengths[++median_index].get_weight(); + } median_length = all_lengths[median_index].get_length(); } else { - median_length = Math::median (all_lengths).get_length(); + median_length = Math::median(all_lengths).get_length(); } } else { median_length = NaN; @@ -196,23 +185,29 @@ void run () default_type ssd = 0.0; for (vector::const_iterator i = all_lengths.begin(); i != all_lengths.end(); ++i) - ssd += i->get_weight() * Math::pow2 (i->get_length() - mean_length); - const float stdev = sum_weights ? (std::sqrt (ssd / (((count - 1) / default_type(count)) * sum_weights))) : NaN; + ssd += i->get_weight() * Math::pow2(i->get_length() - mean_length); + const float stdev = sum_weights ? (std::sqrt(ssd / (((count - 1) / default_type(count)) * sum_weights))) : NaN; vector fields; - auto opt = get_options ("output"); + auto opt = get_options("output"); for (size_t n = 0; n < opt.size(); ++n) - fields.push_back (opt[n][0]); + fields.push_back(opt[n][0]); if (fields.size()) { for (size_t n = 0; n < fields.size(); ++n) { - if (fields[n] == "mean") std::cout << str(mean_length) << " "; - else if (fields[n] == "median") std::cout << str(median_length) << " "; - else if (fields[n] == "std") std::cout << str(stdev) << " "; - else if (fields[n] == "min") std::cout << str(min_length) << " "; - else if (fields[n] == "max") std::cout << str(max_length) << " "; - else if (fields[n] == "count") std::cout << count << " "; + if (fields[n] == "mean") + std::cout << str(mean_length) << " "; + else if (fields[n] == "median") + std::cout << str(median_length) << " "; + else if (fields[n] == "std") + std::cout << str(stdev) << " "; + else if (fields[n] == "min") + std::cout << str(min_length) << " "; + else if (fields[n] == "max") + std::cout << str(max_length) << " "; + else if (fields[n] == "count") + std::cout << count << " "; } std::cout << "\n"; @@ -227,20 +222,17 @@ void run () << " " << std::setw(width) << std::right << "max" << " " << std::setw(width) << std::right << "count\n"; - std::cout << " " << std::setw(width) << std::right << (mean_length) - << " " << std::setw(width) << std::right << (median_length) - << " " << std::setw(width) << std::right << (stdev) - << " " << std::setw(width) << std::right << (min_length) - << " " << std::setw(width) << std::right << (max_length) - << " " << std::setw(width) << std::right << (count) << "\n"; - + std::cout << " " << std::setw(width) << std::right << (mean_length) << " " << std::setw(width) << std::right + << (median_length) << " " << std::setw(width) << std::right << (stdev) << " " << std::setw(width) + << std::right << (min_length) << " " << std::setw(width) << std::right << (max_length) << " " + << std::setw(width) << std::right << (count) << "\n"; } - opt = get_options ("histogram"); + opt = get_options("histogram"); if (opt.size()) { - File::OFStream out (opt[0][0], std::ios_base::out | std::ios_base::trunc); + File::OFStream out(opt[0][0], std::ios_base::out | std::ios_base::trunc); out << "# " << App::command_history_string << "\n"; - if (!std::isfinite (step_size)) + if (!std::isfinite(step_size)) step_size = 1.0f; if (weights_provided) { out << "Length,Sum_weights\n"; @@ -253,5 +245,4 @@ void run () } out << "\n"; } - } diff --git a/cmd/tcktransform.cpp b/cmd/tcktransform.cpp index 791afe8b4b..ed4b7a4145 100644 --- a/cmd/tcktransform.cpp +++ b/cmd/tcktransform.cpp @@ -15,123 +15,103 @@ */ #include "command.h" +#include "dwi/tractography/file.h" +#include "dwi/tractography/properties.h" #include "image.h" +#include "interp/linear.h" #include "ordered_thread_queue.h" #include "progressbar.h" -#include "interp/linear.h" -#include "dwi/tractography/file.h" -#include "dwi/tractography/properties.h" using namespace MR; using namespace MR::DWI; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Apply a spatial transformation to a tracks file"; ARGUMENTS - + Argument ("tracks", "the input track file.").type_tracks_in() - + Argument ("transform", "the image containing the transform.").type_image_in() - + Argument ("output", "the output track file").type_tracks_out(); + +Argument("tracks", "the input track file.").type_tracks_in() + + Argument("transform", "the image containing the transform.").type_image_in() + + Argument("output", "the output track file").type_tracks_out(); } - - using value_type = float; using TrackType = Tractography::Streamline; +class Loader { +public: + Loader(const std::string &file) : reader(file, properties) {} + bool operator()(TrackType &item) { return reader(item); } -class Loader { - public: - Loader (const std::string& file) : reader (file, properties) {} - - bool operator() (TrackType& item) { - return reader (item); - } + Tractography::Properties properties; - Tractography::Properties properties; - protected: - Tractography::Reader reader; +protected: + Tractography::Reader reader; }; - - -class Warper { - public: - Warper (const Image& warp) : - interp (warp) { } - - bool operator () (const TrackType& in, TrackType& out) { - out.clear(); - out.set_index (in.get_index()); - out.weight = in.weight; - for (size_t n = 0; n < in.size(); ++n) { - auto vertex = pos(in[n]); - if (vertex.allFinite()) - out.push_back (vertex); - } - return true; +class Warper { +public: + Warper(const Image &warp) : interp(warp) {} + + bool operator()(const TrackType &in, TrackType &out) { + out.clear(); + out.set_index(in.get_index()); + out.weight = in.weight; + for (size_t n = 0; n < in.size(); ++n) { + auto vertex = pos(in[n]); + if (vertex.allFinite()) + out.push_back(vertex); } - - Eigen::Matrix pos (const Eigen::Matrix& x) { - Eigen::Matrix p; - if (interp.scanner (x)) { - interp.index(3) = 0; p[0] = interp.value(); - interp.index(3) = 1; p[1] = interp.value(); - interp.index(3) = 2; p[2] = interp.value(); - } - return p; + return true; + } + + Eigen::Matrix pos(const Eigen::Matrix &x) { + Eigen::Matrix p; + if (interp.scanner(x)) { + interp.index(3) = 0; + p[0] = interp.value(); + interp.index(3) = 1; + p[1] = interp.value(); + interp.index(3) = 2; + p[2] = interp.value(); } + return p; + } - - protected: - Interp::Linear< Image > interp; +protected: + Interp::Linear> interp; }; - - -class Writer { - public: - Writer (const std::string& file, const Tractography::Properties& properties) : - progress ("applying spatial transformation to tracks", - properties.find("count") == properties.end() ? 0 : to(properties.find ("count")->second)), - writer (file, properties) { } - - bool operator() (const TrackType& item) { - writer (item); - ++progress; - return true; - } - - protected: - ProgressBar progress; - Tractography::Properties properties; - Tractography::Writer writer; +class Writer { +public: + Writer(const std::string &file, const Tractography::Properties &properties) + : progress("applying spatial transformation to tracks", + properties.find("count") == properties.end() ? 0 : to(properties.find("count")->second)), + writer(file, properties) {} + + bool operator()(const TrackType &item) { + writer(item); + ++progress; + return true; + } + +protected: + ProgressBar progress; + Tractography::Properties properties; + Tractography::Writer writer; }; +void run() { + Loader loader(argument[0]); + auto data = Image::open(argument[1]).with_direct_io(3); + Warper warper(data); + Writer writer(argument[2], loader.properties); - -void run () -{ - Loader loader (argument[0]); - - auto data = Image::open (argument[1]).with_direct_io (3); - Warper warper (data); - - Writer writer (argument[2], loader.properties); - - Thread::run_ordered_queue ( - loader, - Thread::batch (TrackType(), 1024), - Thread::multi (warper), - Thread::batch (TrackType(), 1024), - writer); + Thread::run_ordered_queue( + loader, Thread::batch(TrackType(), 1024), Thread::multi(warper), Thread::batch(TrackType(), 1024), writer); } - diff --git a/cmd/tensor2metric.cpp b/cmd/tensor2metric.cpp index f3a81d0fb0..4b8a27812b 100644 --- a/cmd/tensor2metric.cpp +++ b/cmd/tensor2metric.cpp @@ -14,530 +14,537 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "progressbar.h" -#include "image.h" #include "algo/threaded_copy.h" +#include "command.h" +#include "dwi/directions/predefined.h" #include "dwi/gradient.h" #include "dwi/tensor.h" -#include -#include "dwi/directions/predefined.h" #include "file/matrix.h" +#include "image.h" +#include "progressbar.h" +#include using namespace MR; using namespace App; using value_type = float; -const char* modulate_choices[] = { "none", "fa", "eigval", NULL }; +const char *modulate_choices[] = {"none", "fa", "eigval", NULL}; #define DEFAULT_RK_NDIRS 300 -void usage () -{ +void usage() { ARGUMENTS - + Argument ("tensor", "the input tensor image.").type_image_in (); + +Argument("tensor", "the input tensor image.").type_image_in(); OPTIONS - + Option ("mask", - "only perform computation within the specified binary brain mask image.") - + Argument ("image").type_image_in() - - + OptionGroup ("Diffusion Tensor Imaging") - - + Option ("adc", - "compute the mean apparent diffusion coefficient (ADC) of the diffusion tensor. " - "(sometimes also referred to as the mean diffusivity (MD))") - + Argument ("image").type_image_out() - - + Option ("fa", - "compute the fractional anisotropy (FA) of the diffusion tensor.") - + Argument ("image").type_image_out() - - + Option ("ad", - "compute the axial diffusivity (AD) of the diffusion tensor. " - "(equivalent to the principal eigenvalue)") - + Argument ("image").type_image_out() - - + Option ("rd", - "compute the radial diffusivity (RD) of the diffusion tensor. " - "(equivalent to the mean of the two non-principal eigenvalues)") - + Argument ("image").type_image_out() - - + Option ("value", - "compute the selected eigenvalue(s) of the diffusion tensor.") - + Argument ("image").type_image_out() - - + Option ("vector", - "compute the selected eigenvector(s) of the diffusion tensor.") - + Argument ("image").type_image_out() - - + Option ("num", - "specify the desired eigenvalue/eigenvector(s). Note that several eigenvalues " - "can be specified as a number sequence. For example, '1,3' specifies the " - "principal (1) and minor (3) eigenvalues/eigenvectors (default = 1).") - + Argument ("sequence").type_sequence_int() - - + Option ("modulate", - "specify how to modulate the magnitude of the eigenvectors. Valid choices " - "are: none, FA, eigval (default = FA).") - + Argument ("choice").type_choice (modulate_choices) - - + Option ("cl", - "compute the linearity metric of the diffusion tensor. " - "(one of the three Westin shape metrics)") - + Argument ("image").type_image_out() - - + Option ("cp", - "compute the planarity metric of the diffusion tensor. " - "(one of the three Westin shape metrics)") - + Argument ("image").type_image_out() - - + Option ("cs", - "compute the sphericity metric of the diffusion tensor. " - "(one of the three Westin shape metrics)") - + Argument ("image").type_image_out() - - + OptionGroup ("Diffusion Kurtosis Imaging") - - + Option ("dkt", - "input diffusion kurtosis tensor.") - + Argument ("image").type_image_in() - - + Option ("mk", - "compute the mean kurtosis (MK) of the kurtosis tensor.") - + Argument ("image").type_image_out() - - + Option ("ak", - "compute the axial kurtosis (AK) of the kurtosis tensor.") - + Argument ("image").type_image_out() - - + Option ("rk", - "compute the radial kurtosis (RK) of the kurtosis tensor.") - + Argument ("image").type_image_out() - - + Option ("mk_dirs", - "specify the directions used to numerically calculate mean kurtosis " - "(by default, the built-in 300 direction set is used). These should be " - "supplied as a text file containing [ az el ] pairs for the directions.") - + Argument ("file").type_file_in() - - + Option ("rk_ndirs", - "specify the number of directions used to numerically calculate radial kurtosis " - "(by default, " + str(DEFAULT_RK_NDIRS) + " directions are used).") - + Argument ("integer").type_integer (0, 1000); - - AUTHOR = "Ben Jeurissen (ben.jeurissen@uantwerpen.be), Thijs Dhollander (thijs.dhollander@gmail.com) & J-Donald Tournier (jdtournier@gmail.com)"; + +Option("mask", "only perform computation within the specified binary brain mask image.") + + Argument("image").type_image_in() + + + OptionGroup("Diffusion Tensor Imaging") + + + Option("adc", + "compute the mean apparent diffusion coefficient (ADC) of the diffusion tensor. " + "(sometimes also referred to as the mean diffusivity (MD))") + + Argument("image").type_image_out() + + + Option("fa", "compute the fractional anisotropy (FA) of the diffusion tensor.") + + Argument("image").type_image_out() + + + Option("ad", + "compute the axial diffusivity (AD) of the diffusion tensor. " + "(equivalent to the principal eigenvalue)") + + Argument("image").type_image_out() + + + Option("rd", + "compute the radial diffusivity (RD) of the diffusion tensor. " + "(equivalent to the mean of the two non-principal eigenvalues)") + + Argument("image").type_image_out() + + + Option("value", "compute the selected eigenvalue(s) of the diffusion tensor.") + + Argument("image").type_image_out() + + + Option("vector", "compute the selected eigenvector(s) of the diffusion tensor.") + + Argument("image").type_image_out() + + + Option("num", + "specify the desired eigenvalue/eigenvector(s). Note that several eigenvalues " + "can be specified as a number sequence. For example, '1,3' specifies the " + "principal (1) and minor (3) eigenvalues/eigenvectors (default = 1).") + + Argument("sequence").type_sequence_int() + + + Option("modulate", + "specify how to modulate the magnitude of the eigenvectors. Valid choices " + "are: none, FA, eigval (default = FA).") + + Argument("choice").type_choice(modulate_choices) + + + Option("cl", + "compute the linearity metric of the diffusion tensor. " + "(one of the three Westin shape metrics)") + + Argument("image").type_image_out() + + + Option("cp", + "compute the planarity metric of the diffusion tensor. " + "(one of the three Westin shape metrics)") + + Argument("image").type_image_out() + + + Option("cs", + "compute the sphericity metric of the diffusion tensor. " + "(one of the three Westin shape metrics)") + + Argument("image").type_image_out() + + + OptionGroup("Diffusion Kurtosis Imaging") + + + Option("dkt", "input diffusion kurtosis tensor.") + Argument("image").type_image_in() + + + Option("mk", "compute the mean kurtosis (MK) of the kurtosis tensor.") + Argument("image").type_image_out() + + + Option("ak", "compute the axial kurtosis (AK) of the kurtosis tensor.") + Argument("image").type_image_out() + + + Option("rk", "compute the radial kurtosis (RK) of the kurtosis tensor.") + Argument("image").type_image_out() + + + Option("mk_dirs", + "specify the directions used to numerically calculate mean kurtosis " + "(by default, the built-in 300 direction set is used). These should be " + "supplied as a text file containing [ az el ] pairs for the directions.") + + Argument("file").type_file_in() + + + Option("rk_ndirs", + "specify the number of directions used to numerically calculate radial kurtosis " + "(by default, " + + str(DEFAULT_RK_NDIRS) + " directions are used).") + + Argument("integer").type_integer(0, 1000); + + AUTHOR = "Ben Jeurissen (ben.jeurissen@uantwerpen.be), Thijs Dhollander (thijs.dhollander@gmail.com) & J-Donald " + "Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Generate maps of tensor-derived parameters"; REFERENCES - + "Basser, P. J.; Mattiello, J. & Lebihan, D. " - "MR diffusion tensor spectroscopy and imaging. " - "Biophysical Journal, 1994, 66, 259-267" - + "Westin, C. F.; Peled, S.; Gudbjartsson, H.; Kikinis, R. & Jolesz, F. A. " - "Geometrical diffusion measures for MRI from tensor basis analysis. " - "Proc Intl Soc Mag Reson Med, 1997, 5, 1742"; + +"Basser, P. J.; Mattiello, J. & Lebihan, D. " + "MR diffusion tensor spectroscopy and imaging. " + "Biophysical Journal, 1994, 66, 259-267" + + "Westin, C. F.; Peled, S.; Gudbjartsson, H.; Kikinis, R. & Jolesz, F. A. " + "Geometrical diffusion measures for MRI from tensor basis analysis. " + "Proc Intl Soc Mag Reson Med, 1997, 5, 1742"; } class Processor { - public: - Processor (Image& mask_img, - Image& adc_img, - Image& fa_img, - Image& ad_img, - Image& rd_img, - Image& cl_img, - Image& cp_img, - Image& cs_img, - Image& value_img, - Image& vector_img, - Image& dkt_img, - Image& mk_img, - Image& ak_img, - Image& rk_img, - vector& vals, - int modulate, - Eigen::MatrixXd mk_dirs, - int rk_ndirs) : - mask_img (mask_img), - adc_img (adc_img), - fa_img (fa_img), - ad_img (ad_img), - rd_img (rd_img), - cl_img (cl_img), - cp_img (cp_img), - cs_img (cs_img), - value_img (value_img), - vector_img (vector_img), - dkt_img (dkt_img), - mk_img (mk_img), - ak_img (ak_img), - rk_img (rk_img), - vals (vals), - modulate (modulate), - mk_dirs(mk_dirs), - rk_ndirs(rk_ndirs), - need_eigenvalues (value_img.valid() || vector_img.valid() || ad_img.valid() || rd_img.valid() || - cl_img.valid() || cp_img.valid() || cs_img.valid() || ak_img.valid() || rk_img.valid()), - need_eigenvectors (vector_img.valid() || ak_img.valid() || rk_img.valid()), - need_dkt (dkt_img.valid() || mk_img.valid() || ak_img.valid() || rk_img.valid()) { - for (auto& n : this->vals) - --n; - if (mk_img.valid()) - mk_bmat = DWI::grad2bmatrix (mk_dirs, true); - if (rk_img.valid()) { - rk_dirs.resize (rk_ndirs,3); - rk_bmat.resize (rk_ndirs,22); - } - } +public: + Processor(Image &mask_img, + Image &adc_img, + Image &fa_img, + Image &ad_img, + Image &rd_img, + Image &cl_img, + Image &cp_img, + Image &cs_img, + Image &value_img, + Image &vector_img, + Image &dkt_img, + Image &mk_img, + Image &ak_img, + Image &rk_img, + vector &vals, + int modulate, + Eigen::MatrixXd mk_dirs, + int rk_ndirs) + : mask_img(mask_img), + adc_img(adc_img), + fa_img(fa_img), + ad_img(ad_img), + rd_img(rd_img), + cl_img(cl_img), + cp_img(cp_img), + cs_img(cs_img), + value_img(value_img), + vector_img(vector_img), + dkt_img(dkt_img), + mk_img(mk_img), + ak_img(ak_img), + rk_img(rk_img), + vals(vals), + modulate(modulate), + mk_dirs(mk_dirs), + rk_ndirs(rk_ndirs), + need_eigenvalues(value_img.valid() || vector_img.valid() || ad_img.valid() || rd_img.valid() || + cl_img.valid() || cp_img.valid() || cs_img.valid() || ak_img.valid() || rk_img.valid()), + need_eigenvectors(vector_img.valid() || ak_img.valid() || rk_img.valid()), + need_dkt(dkt_img.valid() || mk_img.valid() || ak_img.valid() || rk_img.valid()) { + for (auto &n : this->vals) + --n; + if (mk_img.valid()) + mk_bmat = DWI::grad2bmatrix(mk_dirs, true); + if (rk_img.valid()) { + rk_dirs.resize(rk_ndirs, 3); + rk_bmat.resize(rk_ndirs, 22); + } + } - void operator() (Image& dt_img) - { - /* check mask */ - if (mask_img.valid()) { - assign_pos_of (dt_img, 0, 3).to (mask_img); - if (!mask_img.value()) - return; - } + void operator()(Image &dt_img) { + /* check mask */ + if (mask_img.valid()) { + assign_pos_of(dt_img, 0, 3).to(mask_img); + if (!mask_img.value()) + return; + } - /* input dt */ - Eigen::Matrix dt; - for (dt_img.index(3) = 0; dt_img.index(3) < 6; ++dt_img.index(3)) - dt[dt_img.index(3)] = dt_img.value(); + /* input dt */ + Eigen::Matrix dt; + for (dt_img.index(3) = 0; dt_img.index(3) < 6; ++dt_img.index(3)) + dt[dt_img.index(3)] = dt_img.value(); - /* output adc */ - if (adc_img.valid()) { - assign_pos_of (dt_img, 0, 3).to (adc_img); - adc_img.value() = DWI::tensor2ADC(dt); - } + /* output adc */ + if (adc_img.valid()) { + assign_pos_of(dt_img, 0, 3).to(adc_img); + adc_img.value() = DWI::tensor2ADC(dt); + } - double fa = 0.0; - if (fa_img.valid() || (vector_img.valid() && (modulate == 1))) - fa = DWI::tensor2FA(dt); + double fa = 0.0; + if (fa_img.valid() || (vector_img.valid() && (modulate == 1))) + fa = DWI::tensor2FA(dt); - /* output fa */ - if (fa_img.valid()) { - assign_pos_of (dt_img, 0, 3).to (fa_img); - fa_img.value() = fa; - } + /* output fa */ + if (fa_img.valid()) { + assign_pos_of(dt_img, 0, 3).to(fa_img); + fa_img.value() = fa; + } - Eigen::SelfAdjointEigenSolver es; - if (need_eigenvalues || vector_img.valid()) { - Eigen::Matrix3d M; - M (0,0) = dt[0]; - M (1,1) = dt[1]; - M (2,2) = dt[2]; - M (0,1) = M (1,0) = dt[3]; - M (0,2) = M (2,0) = dt[4]; - M (1,2) = M (2,1) = dt[5]; - es = Eigen::SelfAdjointEigenSolver(M, need_eigenvectors ? Eigen::ComputeEigenvectors : Eigen::EigenvaluesOnly); - } + Eigen::SelfAdjointEigenSolver es; + if (need_eigenvalues || vector_img.valid()) { + Eigen::Matrix3d M; + M(0, 0) = dt[0]; + M(1, 1) = dt[1]; + M(2, 2) = dt[2]; + M(0, 1) = M(1, 0) = dt[3]; + M(0, 2) = M(2, 0) = dt[4]; + M(1, 2) = M(2, 1) = dt[5]; + es = Eigen::SelfAdjointEigenSolver( + M, need_eigenvectors ? Eigen::ComputeEigenvectors : Eigen::EigenvaluesOnly); + } - Eigen::Vector3d eigval; - ssize_t ith_eig[3] = { 2, 1, 0 }; - if (need_eigenvalues) { - eigval = es.eigenvalues(); - ith_eig[0] = 0; ith_eig[1] = 1; ith_eig[2] = 2; - std::sort (std::begin (ith_eig), std::end (ith_eig), - [&eigval](size_t a, size_t b) { return abs(eigval[a]) > abs(eigval[b]); }); - } + Eigen::Vector3d eigval; + ssize_t ith_eig[3] = {2, 1, 0}; + if (need_eigenvalues) { + eigval = es.eigenvalues(); + ith_eig[0] = 0; + ith_eig[1] = 1; + ith_eig[2] = 2; + std::sort(std::begin(ith_eig), std::end(ith_eig), [&eigval](size_t a, size_t b) { + return abs(eigval[a]) > abs(eigval[b]); + }); + } - /* output value */ - if (value_img.valid()) { - assign_pos_of (dt_img, 0, 3).to (value_img); - if (vals.size() > 1) { - auto l = Loop(3)(value_img); - for (size_t i = 0; i < vals.size(); i++) { - value_img.value() = eigval(ith_eig[vals[i]]); l++; - } - } else { - value_img.value() = eigval(ith_eig[vals[0]]); + /* output value */ + if (value_img.valid()) { + assign_pos_of(dt_img, 0, 3).to(value_img); + if (vals.size() > 1) { + auto l = Loop(3)(value_img); + for (size_t i = 0; i < vals.size(); i++) { + value_img.value() = eigval(ith_eig[vals[i]]); + l++; } + } else { + value_img.value() = eigval(ith_eig[vals[0]]); } + } - /* output ad */ - if (ad_img.valid()) { - assign_pos_of (dt_img, 0, 3).to (ad_img); - ad_img.value() = eigval(2); - } + /* output ad */ + if (ad_img.valid()) { + assign_pos_of(dt_img, 0, 3).to(ad_img); + ad_img.value() = eigval(2); + } - /* output rd */ - if (rd_img.valid()) { - assign_pos_of (dt_img, 0, 3).to (rd_img); - rd_img.value() = (eigval(1) + eigval(0)) / 2; - } + /* output rd */ + if (rd_img.valid()) { + assign_pos_of(dt_img, 0, 3).to(rd_img); + rd_img.value() = (eigval(1) + eigval(0)) / 2; + } - /* output shape measures */ - if (cl_img.valid() || cp_img.valid() || cs_img.valid()) { - double eigsum = eigval.sum(); - if (eigsum != 0.0) { - if (cl_img.valid()) { - assign_pos_of (dt_img, 0, 3).to (cl_img); - cl_img.value() = (eigval(2) - eigval(1)) / eigsum; - } - if (cp_img.valid()) { - assign_pos_of (dt_img, 0, 3).to (cp_img); - cp_img.value() = 2.0 * (eigval(1) - eigval(0)) / eigsum; - } - if (cs_img.valid()) { - assign_pos_of (dt_img, 0, 3).to (cs_img); - cs_img.value() = 3.0 * eigval(0) / eigsum; - } + /* output shape measures */ + if (cl_img.valid() || cp_img.valid() || cs_img.valid()) { + double eigsum = eigval.sum(); + if (eigsum != 0.0) { + if (cl_img.valid()) { + assign_pos_of(dt_img, 0, 3).to(cl_img); + cl_img.value() = (eigval(2) - eigval(1)) / eigsum; } - } - - /* output vector */ - if (vector_img.valid()) { - Eigen::Matrix3d eigvec = es.eigenvectors(); - assign_pos_of (dt_img, 0, 3).to (vector_img); - auto l = Loop(3)(vector_img); - for (size_t i = 0; i < vals.size(); i++) { - double fact = 1.0; - if (modulate == 1) - fact = fa; - else if (modulate == 2) - fact = eigval(ith_eig[vals[i]]); - vector_img.value() = eigvec(0,ith_eig[vals[i]])*fact; l++; - vector_img.value() = eigvec(1,ith_eig[vals[i]])*fact; l++; - vector_img.value() = eigvec(2,ith_eig[vals[i]])*fact; l++; + if (cp_img.valid()) { + assign_pos_of(dt_img, 0, 3).to(cp_img); + cp_img.value() = 2.0 * (eigval(1) - eigval(0)) / eigsum; + } + if (cs_img.valid()) { + assign_pos_of(dt_img, 0, 3).to(cs_img); + cs_img.value() = 3.0 * eigval(0) / eigsum; } } + } - /* input dkt */ - Eigen::Matrix dkt; - if (dkt_img.valid()) { - double adc_sq = Math::pow2 (DWI::tensor2ADC(dt)); - assign_pos_of (dt_img, 0, 3).to (dkt_img); - for (auto l = Loop (3) (dkt_img); l; ++l) - dkt[dkt_img.index(3)] = dkt_img.value()*adc_sq; - } - - /* output mk */ - if (mk_img.valid()) { - assign_pos_of (dt_img, 0, 3).to (mk_img); - mk_img.value() = kurtosis (mk_bmat, dt, dkt); + /* output vector */ + if (vector_img.valid()) { + Eigen::Matrix3d eigvec = es.eigenvectors(); + assign_pos_of(dt_img, 0, 3).to(vector_img); + auto l = Loop(3)(vector_img); + for (size_t i = 0; i < vals.size(); i++) { + double fact = 1.0; + if (modulate == 1) + fact = fa; + else if (modulate == 2) + fact = eigval(ith_eig[vals[i]]); + vector_img.value() = eigvec(0, ith_eig[vals[i]]) * fact; + l++; + vector_img.value() = eigvec(1, ith_eig[vals[i]]) * fact; + l++; + vector_img.value() = eigvec(2, ith_eig[vals[i]]) * fact; + l++; } + } - /* output ak */ - if (ak_img.valid()) { - Eigen::Matrix ak_bmat = DWI::grad2bmatrix (es.eigenvectors().col(ith_eig[0]).transpose(), true); - assign_pos_of (dt_img, 0, 3).to (ak_img); - ak_img.value() = kurtosis (ak_bmat, dt, dkt); - } + /* input dkt */ + Eigen::Matrix dkt; + if (dkt_img.valid()) { + double adc_sq = Math::pow2(DWI::tensor2ADC(dt)); + assign_pos_of(dt_img, 0, 3).to(dkt_img); + for (auto l = Loop(3)(dkt_img); l; ++l) + dkt[dkt_img.index(3)] = dkt_img.value() * adc_sq; + } - /* output rk */ - if (rk_img.valid()) { - Eigen::Vector3d dir1 = es.eigenvectors().col(ith_eig[0]); - Eigen::Vector3d dir2 = es.eigenvectors().col(ith_eig[1]); - const double delta = Math::pi/rk_ndirs; - double a = 0; - for (int i = 0; i < rk_ndirs; i++) { - rk_dirs.row(i) = Eigen::AngleAxisd(a, dir1)*dir2; - a += delta; - } - rk_bmat.noalias() = DWI::grad2bmatrix (rk_dirs, true); - assign_pos_of (dt_img, 0, 3).to (rk_img); - rk_img.value() = kurtosis (rk_bmat, dt, dkt); - } + /* output mk */ + if (mk_img.valid()) { + assign_pos_of(dt_img, 0, 3).to(mk_img); + mk_img.value() = kurtosis(mk_bmat, dt, dkt); + } + /* output ak */ + if (ak_img.valid()) { + Eigen::Matrix ak_bmat = + DWI::grad2bmatrix(es.eigenvectors().col(ith_eig[0]).transpose(), true); + assign_pos_of(dt_img, 0, 3).to(ak_img); + ak_img.value() = kurtosis(ak_bmat, dt, dkt); } - private: - Image mask_img; - Image adc_img; - Image fa_img; - Image ad_img; - Image rd_img; - Image cl_img; - Image cp_img; - Image cs_img; - Image value_img; - Image vector_img; - Image dkt_img; - Image mk_img; - Image ak_img; - Image rk_img; - vector vals; - const int modulate; - Eigen::MatrixXd mk_dirs; - Eigen::MatrixXd mk_bmat, rk_bmat; - Eigen::MatrixXd rk_dirs; - const int rk_ndirs; - const bool need_eigenvalues; - const bool need_eigenvectors; - const bool need_dkt; - - - template - double kurtosis (const BMatType& bmat, const DTType& dt, const DKTType& dkt) { - return -6.0 * ( (bmat.template middleCols<15>(7) * dkt).array() / (bmat.template middleCols<6>(1) * dt).array().square() ).mean(); + /* output rk */ + if (rk_img.valid()) { + Eigen::Vector3d dir1 = es.eigenvectors().col(ith_eig[0]); + Eigen::Vector3d dir2 = es.eigenvectors().col(ith_eig[1]); + const double delta = Math::pi / rk_ndirs; + double a = 0; + for (int i = 0; i < rk_ndirs; i++) { + rk_dirs.row(i) = Eigen::AngleAxisd(a, dir1) * dir2; + a += delta; } + rk_bmat.noalias() = DWI::grad2bmatrix(rk_dirs, true); + assign_pos_of(dt_img, 0, 3).to(rk_img); + rk_img.value() = kurtosis(rk_bmat, dt, dkt); + } + } - +private: + Image mask_img; + Image adc_img; + Image fa_img; + Image ad_img; + Image rd_img; + Image cl_img; + Image cp_img; + Image cs_img; + Image value_img; + Image vector_img; + Image dkt_img; + Image mk_img; + Image ak_img; + Image rk_img; + vector vals; + const int modulate; + Eigen::MatrixXd mk_dirs; + Eigen::MatrixXd mk_bmat, rk_bmat; + Eigen::MatrixXd rk_dirs; + const int rk_ndirs; + const bool need_eigenvalues; + const bool need_eigenvectors; + const bool need_dkt; + + template + double kurtosis(const BMatType &bmat, const DTType &dt, const DKTType &dkt) { + return -6.0 * + ((bmat.template middleCols<15>(7) * dkt).array() / (bmat.template middleCols<6>(1) * dt).array().square()) + .mean(); + } }; - - - - - - - -void run () -{ - auto dt_img = Image::open (argument[0]); - Header header (dt_img); - if (header.ndim() != 4 || header.size(3) !=6) { +void run() { + auto dt_img = Image::open(argument[0]); + Header header(dt_img); + if (header.ndim() != 4 || header.size(3) != 6) { throw Exception("input tensor image is not a valid tensor."); } auto mask_img = Image(); - auto opt = get_options ("mask"); + auto opt = get_options("mask"); if (opt.size()) { - mask_img = Image::open (opt[0][0]); - check_dimensions (dt_img, mask_img, 0, 3); + mask_img = Image::open(opt[0][0]); + check_dimensions(dt_img, mask_img, 0, 3); } size_t metric_count = 0; size_t dki_metric_count = 0; auto adc_img = Image(); - opt = get_options ("adc"); + opt = get_options("adc"); if (opt.size()) { header.ndim() = 3; - adc_img = Image::create (opt[0][0], header); + adc_img = Image::create(opt[0][0], header); metric_count++; } auto fa_img = Image(); - opt = get_options ("fa"); + opt = get_options("fa"); if (opt.size()) { header.ndim() = 3; - fa_img = Image::create (opt[0][0], header); + fa_img = Image::create(opt[0][0], header); metric_count++; } auto ad_img = Image(); - opt = get_options ("ad"); + opt = get_options("ad"); if (opt.size()) { header.ndim() = 3; - ad_img = Image::create (opt[0][0], header); + ad_img = Image::create(opt[0][0], header); metric_count++; } auto rd_img = Image(); - opt = get_options ("rd"); + opt = get_options("rd"); if (opt.size()) { header.ndim() = 3; - rd_img = Image::create (opt[0][0], header); + rd_img = Image::create(opt[0][0], header); metric_count++; } auto cl_img = Image(); - opt = get_options ("cl"); + opt = get_options("cl"); if (opt.size()) { header.ndim() = 3; - cl_img = Image::create (opt[0][0], header); + cl_img = Image::create(opt[0][0], header); metric_count++; } auto cp_img = Image(); - opt = get_options ("cp"); + opt = get_options("cp"); if (opt.size()) { header.ndim() = 3; - cp_img = Image::create (opt[0][0], header); + cp_img = Image::create(opt[0][0], header); metric_count++; } auto cs_img = Image(); - opt = get_options ("cs"); + opt = get_options("cs"); if (opt.size()) { header.ndim() = 3; - cs_img = Image::create (opt[0][0], header); + cs_img = Image::create(opt[0][0], header); metric_count++; } vector vals = {1}; - opt = get_options ("num"); + opt = get_options("num"); if (opt.size()) { - vals = parse_ints (opt[0][0]); + vals = parse_ints(opt[0][0]); if (vals.empty()) - throw Exception ("invalid eigenvalue/eigenvector number specifier"); + throw Exception("invalid eigenvalue/eigenvector number specifier"); for (size_t i = 0; i < vals.size(); ++i) if (vals[i] < 1 || vals[i] > 3) - throw Exception ("eigenvalue/eigenvector number is out of bounds"); + throw Exception("eigenvalue/eigenvector number is out of bounds"); } - float modulate = get_option_value ("modulate", 1); + float modulate = get_option_value("modulate", 1); auto value_img = Image(); - opt = get_options ("value"); + opt = get_options("value"); if (opt.size()) { header.ndim() = 3; - if (vals.size()>1) { + if (vals.size() > 1) { header.ndim() = 4; - header.size (3) = vals.size(); + header.size(3) = vals.size(); } - value_img = Image::create (opt[0][0], header); + value_img = Image::create(opt[0][0], header); metric_count++; } auto vector_img = Image(); - opt = get_options ("vector"); + opt = get_options("vector"); if (opt.size()) { header.ndim() = 4; - header.size (3) = vals.size()*3; - vector_img = Image::create (opt[0][0], header); + header.size(3) = vals.size() * 3; + vector_img = Image::create(opt[0][0], header); metric_count++; } auto dkt_img = Image(); - opt = get_options ("dkt"); + opt = get_options("dkt"); if (opt.size()) { - dkt_img = Image::open (opt[0][0]); - check_dimensions (dt_img, dkt_img, 0, 3); + dkt_img = Image::open(opt[0][0]); + check_dimensions(dt_img, dkt_img, 0, 3); } auto mk_img = Image(); - opt = get_options ("mk"); + opt = get_options("mk"); if (opt.size()) { header.ndim() = 3; - mk_img = Image::create (opt[0][0], header); + mk_img = Image::create(opt[0][0], header); metric_count++; dki_metric_count++; } auto ak_img = Image(); - opt = get_options ("ak"); + opt = get_options("ak"); if (opt.size()) { header.ndim() = 3; - ak_img = Image::create (opt[0][0], header); + ak_img = Image::create(opt[0][0], header); metric_count++; dki_metric_count++; } auto rk_img = Image(); - opt = get_options ("rk"); + opt = get_options("rk"); if (opt.size()) { header.ndim() = 3; - rk_img = Image::create (opt[0][0], header); + rk_img = Image::create(opt[0][0], header); metric_count++; dki_metric_count++; } - opt = get_options ("mk_dirs"); - const Eigen::MatrixXd mk_dirs = opt.size() ? - File::Matrix::load_matrix (opt[0][0]) : - Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()); + opt = get_options("mk_dirs"); + const Eigen::MatrixXd mk_dirs = + opt.size() ? File::Matrix::load_matrix(opt[0][0]) + : Math::Sphere::spherical2cartesian(DWI::Directions::electrostatic_repulsion_300()); - auto rk_ndirs = get_option_value ("rk_ndirs", DEFAULT_RK_NDIRS); + auto rk_ndirs = get_option_value("rk_ndirs", DEFAULT_RK_NDIRS); if (dki_metric_count && !dkt_img.valid()) { - throw Exception ("Cannot calculate diffusion kurtosis metrics; must provide the kurtosis tensor using the -dkt input option"); + throw Exception( + "Cannot calculate diffusion kurtosis metrics; must provide the kurtosis tensor using the -dkt input option"); } if (!metric_count) - throw Exception ("No output specified; must request at least one metric of interest using the available command-line options"); - - ThreadedLoop (std::string("computing metric") + (metric_count > 1 ? "s" : ""), dt_img, 0, 3) - .run (Processor (mask_img, adc_img, fa_img, ad_img, rd_img, cl_img, cp_img, cs_img, value_img, vector_img, dkt_img, mk_img, ak_img, rk_img, vals, modulate, mk_dirs, rk_ndirs), dt_img); + throw Exception( + "No output specified; must request at least one metric of interest using the available command-line options"); + + ThreadedLoop(std::string("computing metric") + (metric_count > 1 ? "s" : ""), dt_img, 0, 3) + .run(Processor(mask_img, + adc_img, + fa_img, + ad_img, + rd_img, + cl_img, + cp_img, + cs_img, + value_img, + vector_img, + dkt_img, + mk_img, + ak_img, + rk_img, + vals, + modulate, + mk_dirs, + rk_ndirs), + dt_img); } diff --git a/cmd/transformcalc.cpp b/cmd/transformcalc.cpp index 02125bd7a6..c6989862b5 100644 --- a/cmd/transformcalc.cpp +++ b/cmd/transformcalc.cpp @@ -14,99 +14,96 @@ * For more details, see http://www.mrtrix.org/. */ -#include -#include -#include #include "command.h" -#include "math/math.h" +#include "file/key_value.h" #include "file/matrix.h" -#include "math/average_space.h" -#include "image.h" #include "file/nifti_utils.h" +#include "image.h" +#include "math/average_space.h" +#include "math/math.h" #include "transform.h" -#include "file/key_value.h" - +#include +#include +#include using namespace MR; using namespace App; -const char* operations[] = { - "invert", - "half", - "rigid", - "header", - "average", - "interpolate", - "decompose", - "align_vertices_rigid", - "align_vertices_rigid_scale", - NULL -}; - -void usage () -{ +const char *operations[] = {"invert", + "half", + "rigid", + "header", + "average", + "interpolate", + "decompose", + "align_vertices_rigid", + "align_vertices_rigid_scale", + NULL}; + +void usage() { AUTHOR = "Max Pietsch (maximilian.pietsch@kcl.ac.uk)"; SYNOPSIS = "Perform calculations on linear transformation matrices"; ARGUMENTS - + Argument ("inputs", "the input(s) for the specified operation").allow_multiple() - + Argument ("operation", "the operation to perform, one of: " + join(operations, ", ") + " (see description section for details).").type_choice (operations) - + Argument ("output", "the output transformation matrix.").type_file_out (); + +Argument("inputs", "the input(s) for the specified operation").allow_multiple() + + Argument("operation", + "the operation to perform, one of: " + join(operations, ", ") + + " (see description section for details).") + .type_choice(operations) + + Argument("output", "the output transformation matrix.").type_file_out(); EXAMPLES - + Example ("Invert a transformation", - "transformcalc matrix_in.txt invert matrix_out.txt", - "") - - + Example ("Calculate the matrix square root of the input transformation (halfway transformation)", - "transformcalc matrix_in.txt half matrix_out.txt", - "") - - + Example ("Calculate the rigid component of an affine input transformation", - "transformcalc affine_in.txt rigid rigid_out.txt", - "") - - + Example ("Calculate the transformation matrix from an original image and an image with modified header", - "transformcalc mov mapmovhdr header output", - "") - - + Example ("Calculate the average affine matrix of a set of input matrices", - "transformcalc input1.txt ... inputN.txt average matrix_out.txt", - "") - - + Example ("Create interpolated transformation matrix between two inputs", - "transformcalc input1.txt input2.txt interpolate matrix_out.txt", - "Based on matrix decomposition with linear interpolation of " - "translation, rotation and stretch described in: " - "Shoemake, K., Hill, M., & Duff, T. (1992). Matrix Animation and Polar Decomposition. " - "Matrix, 92, 258-264. doi:10.1.1.56.1336") - - + Example ("Decompose transformation matrix M into translation, rotation and stretch and shear (M = T * R * S)", - "transformcalc matrix_in.txt decompose matrixes_out.txt", - "The output is a key-value text file containing: " - "scaling: vector of 3 scaling factors in x, y, z direction; " - "shear: list of shear factors for xy, xz, yz axes; " - "angles: list of Euler angles about static x, y, z axes in radians in the range [0:pi]x[-pi:pi]x[-pi:pi]; " - "angle_axis: angle in radians and rotation axis; " - "translation : translation vector along x, y, z axes in mm; " - "R: composed roation matrix (R = rot_x * rot_y * rot_z); " - "S: composed scaling and shear matrix") - - + Example ("Calculate transformation that aligns two images based on sets of corresponding landmarks", - "transformcalc input moving.txt fixed.txt align_vertices_rigid rigid.txt", - "Similary, 'align_vertices_rigid_scale' produces an affine matrix (rigid and global scale). " - "Vertex coordinates are in scanner space, corresponding vertices must be stored in the same row " - "of moving.txt and fixed.txt. Requires 3 or more vertices in each file. " - "Algorithm: Kabsch 'A solution for the best rotation to relate two sets of vectors' DOI:10.1107/S0567739476001873"); - + +Example("Invert a transformation", "transformcalc matrix_in.txt invert matrix_out.txt", "") + + + Example("Calculate the matrix square root of the input transformation (halfway transformation)", + "transformcalc matrix_in.txt half matrix_out.txt", + "") + + + Example("Calculate the rigid component of an affine input transformation", + "transformcalc affine_in.txt rigid rigid_out.txt", + "") + + + Example("Calculate the transformation matrix from an original image and an image with modified header", + "transformcalc mov mapmovhdr header output", + "") + + + Example("Calculate the average affine matrix of a set of input matrices", + "transformcalc input1.txt ... inputN.txt average matrix_out.txt", + "") + + + Example("Create interpolated transformation matrix between two inputs", + "transformcalc input1.txt input2.txt interpolate matrix_out.txt", + "Based on matrix decomposition with linear interpolation of " + "translation, rotation and stretch described in: " + "Shoemake, K., Hill, M., & Duff, T. (1992). Matrix Animation and Polar Decomposition. " + "Matrix, 92, 258-264. doi:10.1.1.56.1336") + + + Example( + "Decompose transformation matrix M into translation, rotation and stretch and shear (M = T * R * S)", + "transformcalc matrix_in.txt decompose matrixes_out.txt", + "The output is a key-value text file containing: " + "scaling: vector of 3 scaling factors in x, y, z direction; " + "shear: list of shear factors for xy, xz, yz axes; " + "angles: list of Euler angles about static x, y, z axes in radians in the range [0:pi]x[-pi:pi]x[-pi:pi]; " + "angle_axis: angle in radians and rotation axis; " + "translation : translation vector along x, y, z axes in mm; " + "R: composed roation matrix (R = rot_x * rot_y * rot_z); " + "S: composed scaling and shear matrix") + + + Example("Calculate transformation that aligns two images based on sets of corresponding landmarks", + "transformcalc input moving.txt fixed.txt align_vertices_rigid rigid.txt", + "Similary, 'align_vertices_rigid_scale' produces an affine matrix (rigid and global scale). " + "Vertex coordinates are in scanner space, corresponding vertices must be stored in the same row " + "of moving.txt and fixed.txt. Requires 3 or more vertices in each file. " + "Algorithm: Kabsch 'A solution for the best rotation to relate two sets of vectors' " + "DOI:10.1107/S0567739476001873"); } -template int sgn(T val) { - return (T(0) < val) - (val < T(0)); -} +template int sgn(T val) { return (T(0) < val) - (val < T(0)); } -transform_type align_corresponding_vertices (const Eigen::MatrixXd &src_vertices, const Eigen::MatrixXd &trg_vertices, bool scale) { +transform_type +align_corresponding_vertices(const Eigen::MatrixXd &src_vertices, const Eigen::MatrixXd &trg_vertices, bool scale) { // this function aligns two sets of vertices which must have corresponding vertices stored in the same row // // scale == false --> Kabsch @@ -118,7 +115,7 @@ transform_type align_corresponding_vertices (const Eigen::MatrixXd &src_vertices assert(src_vertices.rows() == trg_vertices.rows()); const size_t n = trg_vertices.rows(); if (n < 3) - throw Exception ("vertex alignment requires at least 3 points"); + throw Exception("vertex alignment requires at least 3 points"); assert(src_vertices.cols() == trg_vertices.cols()); assert(src_vertices.cols() == 3 && "align_corresponding_vertices implemented only for 3D data"); @@ -127,16 +124,16 @@ transform_type align_corresponding_vertices (const Eigen::MatrixXd &src_vertices Eigen::VectorXd src_centre = src_vertices.colwise().mean(); Eigen::MatrixXd trg_centred = trg_vertices.rowwise() - trg_centre.transpose(); Eigen::MatrixXd src_centred = src_vertices.rowwise() - src_centre.transpose(); - Eigen::MatrixXd cov = (src_centred.adjoint() * trg_centred) / default_type (n - 1); + Eigen::MatrixXd cov = (src_centred.adjoint() * trg_centred) / default_type(n - 1); - Eigen::JacobiSVD svd (cov, Eigen::ComputeFullU | Eigen::ComputeFullV); + Eigen::JacobiSVD svd(cov, Eigen::ComputeFullU | Eigen::ComputeFullV); // rotation matrix Eigen::Matrix3d R = svd.matrixV() * svd.matrixU().transpose(); // calculate determinant of V*U^T to disambiguate rotation sign default_type f_det = R.determinant(); - Eigen::Vector3d e(1, 1, (f_det < 0)? -1 : 1); + Eigen::Vector3d e(1, 1, (f_det < 0) ? -1 : 1); // recompute the rotation if the determinant was negative if (f_det < 0) @@ -148,13 +145,13 @@ transform_type align_corresponding_vertices (const Eigen::MatrixXd &src_vertices if (scale) { default_type fsq_t = 0.0; default_type fsq_s = 0.0; - for (size_t i = 0; i < n; ++ i) { + for (size_t i = 0; i < n; ++i) { fsq_t += trg_centred.row(i).squaredNorm(); fsq_s += src_centred.row(i).squaredNorm(); } // calculate and apply the scale default_type fscale = sqrt(fsq_t / fsq_s); // Umeyama: svd.singularValues().dot(e) / fsq; - DEBUG("scaling: "+str(fscale)); + DEBUG("scaling: " + str(fscale)); R *= fscale; } @@ -167,160 +164,160 @@ transform_type align_corresponding_vertices (const Eigen::MatrixXd &src_vertices return T1 * T2 * T3; } -void run () -{ +void run() { const size_t num_inputs = argument.size() - 2; const int op = argument[num_inputs]; - const std::string& output_path = argument.back(); + const std::string &output_path = argument.back(); switch (op) { - case 0: { // invert - if (num_inputs != 1) - throw Exception ("invert requires 1 input"); - transform_type input = File::Matrix::load_transform (argument[0]); - File::Matrix::save_transform (input.inverse(), output_path); - break; - } - case 1: { // half - if (num_inputs != 1) - throw Exception ("half requires 1 input"); - Eigen::Transform input = File::Matrix::load_transform (argument[0]); - transform_type output; - Eigen::Matrix half = input.matrix().sqrt(); - output.matrix() = half.topLeftCorner(3,4); - File::Matrix::save_transform (output, output_path); - break; - } - case 2: { // rigid - if (num_inputs != 1) - throw Exception ("rigid requires 1 input"); - transform_type input = File::Matrix::load_transform (argument[0]); - transform_type output (input); - output.linear() = input.rotation(); - File::Matrix::save_transform (output, output_path); - break; - } - case 3: { // header - if (num_inputs != 2) - throw Exception ("header requires 2 inputs"); - auto orig_header = Header::open (argument[0]); - auto modified_header = Header::open (argument[1]); - - transform_type forward_transform = Transform(modified_header).voxel2scanner * Transform(orig_header).voxel2scanner.inverse(); - File::Matrix::save_transform (forward_transform.inverse(), output_path); - break; - } - case 4: { // average - if (num_inputs < 2) - throw Exception ("average requires at least 2 inputs"); - transform_type transform_out; - Eigen::Transform Tin; - Eigen::MatrixXd Min; - vector matrices; - for (size_t i = 0; i < num_inputs; i++) { - DEBUG(str(argument[i])); - Tin = File::Matrix::load_transform (argument[i]); - matrices.push_back(Tin.matrix()); - } - - Eigen::MatrixXd average_matrix; - Math::matrix_average ( matrices, average_matrix); - transform_out.matrix() = average_matrix.topLeftCorner(3,4); - File::Matrix::save_transform (transform_out, output_path); - break; - } - case 5: { // interpolate - if (num_inputs != 3) - throw Exception ("interpolation requires 3 inputs"); - transform_type transform1 = File::Matrix::load_transform (argument[0]); - transform_type transform2 = File::Matrix::load_transform (argument[1]); - default_type t = parse_floats(argument[2])[0]; - - transform_type transform_out; - - if (t < 0.0 || t > 1.0) - throw Exception ("t has to be in the interval [0,1]"); - - Eigen::MatrixXd M1 = transform1.linear(); - Eigen::MatrixXd M2 = transform2.linear(); - if (sgn(M1.determinant()) != sgn(M2.determinant())) - WARN("transformation determinants have different signs"); - - Eigen::Matrix3d R1 = transform1.rotation(); - Eigen::Matrix3d R2 = transform2.rotation(); - Eigen::Quaterniond Q1(R1); - Eigen::Quaterniond Q2(R2); - Eigen::Quaterniond Qout; - - // stretch (shear becomes roation and stretch) - Eigen::MatrixXd S1 = R1.transpose() * M1; - Eigen::MatrixXd S2 = R2.transpose() * M2; - if (!M1.isApprox(R1*S1)) - WARN ("M1 matrix decomposition might have failed"); - if (!M2.isApprox(R2*S2)) - WARN ("M2 matrix decomposition might have failed"); - - transform_out.translation() = ((1.0 - t) * transform1.translation() + t * transform2.translation()); - Qout = Q1.slerp(t, Q2); - transform_out.linear() = Qout * ((1 - t) * S1 + t * S2); - INFO("\n"+str(transform_out.matrix().format( - Eigen::IOFormat(Eigen::FullPrecision, 0, ", ", ",\n", "[", "]", "[", "]")))); - File::Matrix::save_transform (transform_out, output_path); - break; - } - case 6: { // decompose - if (num_inputs != 1) - throw Exception ("decomposition requires 1 input"); - transform_type transform = File::Matrix::load_transform (argument[0]); - - Eigen::MatrixXd M = transform.linear(); - Eigen::Matrix3d R = transform.rotation(); - Eigen::MatrixXd S = R.transpose() * M; - if (!M.isApprox(R*S)) - WARN ("matrix decomposition might have failed"); - - Eigen::Vector3d euler_angles = R.eulerAngles(0, 1, 2); - assert (R.isApprox((Eigen::AngleAxisd(euler_angles[0], Eigen::Vector3d::UnitX()) - * Eigen::AngleAxisd(euler_angles[1], Eigen::Vector3d::UnitY()) - * Eigen::AngleAxisd(euler_angles[2], Eigen::Vector3d::UnitZ())).matrix())); - - Eigen::RowVector4d angle_axis; - { - auto AA = Eigen::AngleAxis (R); - angle_axis(0) = AA.angle(); - angle_axis.block<1,3>(0,1) = AA.axis(); - } - - - File::OFStream out (output_path); - out << "# " << App::command_history_string << "\n"; - Eigen::IOFormat fmt(Eigen::FullPrecision, Eigen::DontAlignCols, " ", "\n", "", "", "", "\n"); - out << "scaling: " << Eigen::RowVector3d(S(0,0), S(1,1), S(2,2)).format(fmt); - out << "shear: " << Eigen::RowVector3d(S(0,1), S(0,2), S(1,2)).format(fmt); - out << "angles: " << euler_angles.transpose().format(fmt); - out << "angle_axis: " << angle_axis.format(fmt); - out << "translation: " << transform.translation().transpose().format(fmt); - out << "R: " << R.row(0).format(fmt); - out << "R: " << R.row(1).format(fmt); - out << "R: " << R.row(2).format(fmt); - out << "S: " << S.row(0).format(fmt); - out << "S: " << S.row(1).format(fmt); - out << "S: " << S.row(2).format(fmt); - out << "jacobian_det: " << str(transform.linear().topLeftCorner<3,3>().determinant()) << "\n"; - - break; - } - case 7: case 8: { // align_vertices_rigid and align_vertices_rigid_scale - if (num_inputs != 2) - throw Exception ("align_vertices_rigid requires 2 inputs"); - const Eigen::MatrixXd target_vertices = File::Matrix::load_matrix (argument[0]); - const Eigen::MatrixXd moving_vertices = File::Matrix::load_matrix (argument[1]); - const transform_type T = align_corresponding_vertices (moving_vertices, target_vertices, op==8); - File::Matrix::save_transform (T, output_path); - break; - } - default: assert (0); + case 0: { // invert + if (num_inputs != 1) + throw Exception("invert requires 1 input"); + transform_type input = File::Matrix::load_transform(argument[0]); + File::Matrix::save_transform(input.inverse(), output_path); + break; + } + case 1: { // half + if (num_inputs != 1) + throw Exception("half requires 1 input"); + Eigen::Transform input = File::Matrix::load_transform(argument[0]); + transform_type output; + Eigen::Matrix half = input.matrix().sqrt(); + output.matrix() = half.topLeftCorner(3, 4); + File::Matrix::save_transform(output, output_path); + break; + } + case 2: { // rigid + if (num_inputs != 1) + throw Exception("rigid requires 1 input"); + transform_type input = File::Matrix::load_transform(argument[0]); + transform_type output(input); + output.linear() = input.rotation(); + File::Matrix::save_transform(output, output_path); + break; } + case 3: { // header + if (num_inputs != 2) + throw Exception("header requires 2 inputs"); + auto orig_header = Header::open(argument[0]); + auto modified_header = Header::open(argument[1]); + + transform_type forward_transform = + Transform(modified_header).voxel2scanner * Transform(orig_header).voxel2scanner.inverse(); + File::Matrix::save_transform(forward_transform.inverse(), output_path); + break; + } + case 4: { // average + if (num_inputs < 2) + throw Exception("average requires at least 2 inputs"); + transform_type transform_out; + Eigen::Transform Tin; + Eigen::MatrixXd Min; + vector matrices; + for (size_t i = 0; i < num_inputs; i++) { + DEBUG(str(argument[i])); + Tin = File::Matrix::load_transform(argument[i]); + matrices.push_back(Tin.matrix()); + } + Eigen::MatrixXd average_matrix; + Math::matrix_average(matrices, average_matrix); + transform_out.matrix() = average_matrix.topLeftCorner(3, 4); + File::Matrix::save_transform(transform_out, output_path); + break; + } + case 5: { // interpolate + if (num_inputs != 3) + throw Exception("interpolation requires 3 inputs"); + transform_type transform1 = File::Matrix::load_transform(argument[0]); + transform_type transform2 = File::Matrix::load_transform(argument[1]); + default_type t = parse_floats(argument[2])[0]; + + transform_type transform_out; + + if (t < 0.0 || t > 1.0) + throw Exception("t has to be in the interval [0,1]"); + + Eigen::MatrixXd M1 = transform1.linear(); + Eigen::MatrixXd M2 = transform2.linear(); + if (sgn(M1.determinant()) != sgn(M2.determinant())) + WARN("transformation determinants have different signs"); + + Eigen::Matrix3d R1 = transform1.rotation(); + Eigen::Matrix3d R2 = transform2.rotation(); + Eigen::Quaterniond Q1(R1); + Eigen::Quaterniond Q2(R2); + Eigen::Quaterniond Qout; + + // stretch (shear becomes roation and stretch) + Eigen::MatrixXd S1 = R1.transpose() * M1; + Eigen::MatrixXd S2 = R2.transpose() * M2; + if (!M1.isApprox(R1 * S1)) + WARN("M1 matrix decomposition might have failed"); + if (!M2.isApprox(R2 * S2)) + WARN("M2 matrix decomposition might have failed"); + + transform_out.translation() = ((1.0 - t) * transform1.translation() + t * transform2.translation()); + Qout = Q1.slerp(t, Q2); + transform_out.linear() = Qout * ((1 - t) * S1 + t * S2); + INFO("\n" + + str(transform_out.matrix().format(Eigen::IOFormat(Eigen::FullPrecision, 0, ", ", ",\n", "[", "]", "[", "]")))); + File::Matrix::save_transform(transform_out, output_path); + break; + } + case 6: { // decompose + if (num_inputs != 1) + throw Exception("decomposition requires 1 input"); + transform_type transform = File::Matrix::load_transform(argument[0]); + + Eigen::MatrixXd M = transform.linear(); + Eigen::Matrix3d R = transform.rotation(); + Eigen::MatrixXd S = R.transpose() * M; + if (!M.isApprox(R * S)) + WARN("matrix decomposition might have failed"); + + Eigen::Vector3d euler_angles = R.eulerAngles(0, 1, 2); + assert(R.isApprox((Eigen::AngleAxisd(euler_angles[0], Eigen::Vector3d::UnitX()) * + Eigen::AngleAxisd(euler_angles[1], Eigen::Vector3d::UnitY()) * + Eigen::AngleAxisd(euler_angles[2], Eigen::Vector3d::UnitZ())) + .matrix())); + + Eigen::RowVector4d angle_axis; + { + auto AA = Eigen::AngleAxis(R); + angle_axis(0) = AA.angle(); + angle_axis.block<1, 3>(0, 1) = AA.axis(); + } + File::OFStream out(output_path); + out << "# " << App::command_history_string << "\n"; + Eigen::IOFormat fmt(Eigen::FullPrecision, Eigen::DontAlignCols, " ", "\n", "", "", "", "\n"); + out << "scaling: " << Eigen::RowVector3d(S(0, 0), S(1, 1), S(2, 2)).format(fmt); + out << "shear: " << Eigen::RowVector3d(S(0, 1), S(0, 2), S(1, 2)).format(fmt); + out << "angles: " << euler_angles.transpose().format(fmt); + out << "angle_axis: " << angle_axis.format(fmt); + out << "translation: " << transform.translation().transpose().format(fmt); + out << "R: " << R.row(0).format(fmt); + out << "R: " << R.row(1).format(fmt); + out << "R: " << R.row(2).format(fmt); + out << "S: " << S.row(0).format(fmt); + out << "S: " << S.row(1).format(fmt); + out << "S: " << S.row(2).format(fmt); + out << "jacobian_det: " << str(transform.linear().topLeftCorner<3, 3>().determinant()) << "\n"; + + break; + } + case 7: + case 8: { // align_vertices_rigid and align_vertices_rigid_scale + if (num_inputs != 2) + throw Exception("align_vertices_rigid requires 2 inputs"); + const Eigen::MatrixXd target_vertices = File::Matrix::load_matrix(argument[0]); + const Eigen::MatrixXd moving_vertices = File::Matrix::load_matrix(argument[1]); + const transform_type T = align_corresponding_vertices(moving_vertices, target_vertices, op == 8); + File::Matrix::save_transform(T, output_path); + break; + } + default: + assert(0); + } } diff --git a/cmd/transformcompose.cpp b/cmd/transformcompose.cpp index f03f60e16e..033e126c1b 100644 --- a/cmd/transformcompose.cpp +++ b/cmd/transformcompose.cpp @@ -14,159 +14,153 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "image.h" #include "algo/threaded_loop.h" +#include "command.h" #include "file/matrix.h" +#include "image.h" #include "interp/linear.h" using namespace MR; using namespace App; - -class TransformBase { - public: - virtual ~TransformBase(){} - virtual Eigen::Vector3d transform_point (const Eigen::Vector3d& input) = 0; +class TransformBase { +public: + virtual ~TransformBase() {} + virtual Eigen::Vector3d transform_point(const Eigen::Vector3d &input) = 0; }; +class Warp : public TransformBase { +public: + Warp(Image &in) : interp(in) {} + + Eigen::Vector3d transform_point(const Eigen::Vector3d &input) { + Eigen::Vector3d output; + if (interp.scanner(input)) + output = interp.row(3); + else + output.fill(NaN); + return output; + } -class Warp : public TransformBase { - public: - Warp (Image& in) : interp (in) {} - - Eigen::Vector3d transform_point (const Eigen::Vector3d &input) { - Eigen::Vector3d output; - if (interp.scanner (input)) - output = interp.row(3); - else - output.fill (NaN); - return output; - } - - protected: - Interp::Linear > interp; +protected: + Interp::Linear> interp; }; +class Linear : public TransformBase { +public: + Linear(const transform_type &transform) : transform(transform) {} -class Linear : public TransformBase { - public: - Linear (const transform_type& transform) : transform (transform) {} - - Eigen::Vector3d transform_point (const Eigen::Vector3d &input) { - Eigen::Vector3d output = transform * input; - return output; - } + Eigen::Vector3d transform_point(const Eigen::Vector3d &input) { + Eigen::Vector3d output = transform * input; + return output; + } - const transform_type transform; + const transform_type transform; }; - -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Compose any number of linear transformations and/or warps into a single transformation"; DESCRIPTION - + "Any linear transforms must be supplied as a 4x4 matrix in a text file (e.g. as per the output of mrregister). " - "Any warp fields must be supplied as a 4D image representing a deformation field (e.g. as output from mrrregister -nl_warp)." + +"Any linear transforms must be supplied as a 4x4 matrix in a text file (e.g. as per the output of mrregister). " + "Any warp fields must be supplied as a 4D image representing a deformation field (e.g. as output from mrrregister " + "-nl_warp)." - + "Input transformations should be provided to the command in the order in which they would be applied " - "to an image if they were to be applied individually." + + "Input transformations should be provided to the command in the order in which they would be applied " + "to an image if they were to be applied individually." - + "If all input transformations are linear, and the -template option is not provided, then the file output by " - "the command will also be a linear transformation saved as a 4x4 matrix in a text file. If a template image is " - "supplied, then the output will always be a deformation field. If at least one of the inputs is a warp field, " - "then the output will be a deformation field, which will be defined on the grid of the last input warp image " - "supplied if the -template option is not used."; + + "If all input transformations are linear, and the -template option is not provided, then the file output by " + "the command will also be a linear transformation saved as a 4x4 matrix in a text file. If a template image is " + "supplied, then the output will always be a deformation field. If at least one of the inputs is a warp field, " + "then the output will be a deformation field, which will be defined on the grid of the last input warp image " + "supplied if the -template option is not used."; ARGUMENTS - + Argument ("input", "the input transforms (either linear or non-linear warps).").type_file_in().allow_multiple() - + Argument ("output", "the output file (may be a linear transformation text file, or a deformation warp field image, depending on usage)").type_various(); + +Argument("input", "the input transforms (either linear or non-linear warps).").type_file_in().allow_multiple() + + Argument("output", + "the output file (may be a linear transformation text file, or a deformation warp field image, " + "depending on usage)") + .type_various(); OPTIONS - + Option ("template", "define the output grid defined by a template image") - + Argument ("image").type_image_in(); - + +Option("template", "define the output grid defined by a template image") + Argument("image").type_image_in(); } - using value_type = float; - -void run () -{ +void run() { vector> transform_list; std::unique_ptr
template_header; for (size_t i = 0; i < argument.size() - 1; ++i) { try { - template_header.reset (new Header (Header::open (argument[i]))); - auto image = Image::open (argument[i]); + template_header.reset(new Header(Header::open(argument[i]))); + auto image = Image::open(argument[i]); if (image.ndim() != 4) - throw Exception ("input warp is not a 4D image"); + throw Exception("input warp is not a 4D image"); if (image.size(3) != 3) - throw Exception ("input warp should have 3 volumes in the 4th dimension"); + throw Exception("input warp should have 3 volumes in the 4th dimension"); - std::unique_ptr transform (new Warp (image)); - transform_list.push_back (std::move (transform)); + std::unique_ptr transform(new Warp(image)); + transform_list.push_back(std::move(transform)); - } catch (Exception& E) { + } catch (Exception &E) { try { - std::unique_ptr transform (new Linear (File::Matrix::load_transform (argument[i]))); - transform_list.push_back (std::move (transform)); - } catch (Exception& E) { - throw Exception ("error reading input file: " + str(argument[i]) + ". Does not appear to be a 4D warp image or 4x4 linear transform."); + std::unique_ptr transform(new Linear(File::Matrix::load_transform(argument[i]))); + transform_list.push_back(std::move(transform)); + } catch (Exception &E) { + throw Exception("error reading input file: " + str(argument[i]) + + ". Does not appear to be a 4D warp image or 4x4 linear transform."); } } - } auto opt = get_options("template"); if (opt.size()) { - template_header.reset (new Header (Header::open (opt[0][0]))); - // no template is supplied and there are input warps, then make sure the last transform in the list is a warp + template_header.reset(new Header(Header::open(opt[0][0]))); + // no template is supplied and there are input warps, then make sure the last transform in the list is a warp } else if (template_header) { - if (!dynamic_cast (transform_list[transform_list.size() - 1].get())) - throw Exception ("Output deformation field grid not defined. When composing warps either use the -template " - "option to define the output deformation field grid, or ensure the last input transformation is a warp."); + if (!dynamic_cast(transform_list[transform_list.size() - 1].get())) + throw Exception( + "Output deformation field grid not defined. When composing warps either use the -template " + "option to define the output deformation field grid, or ensure the last input transformation is a warp."); } // all inputs are linear so compose and output as text file if (!template_header) { - transform_type composed = dynamic_cast(transform_list[transform_list.size() - 1].get())->transform; + transform_type composed = dynamic_cast(transform_list[transform_list.size() - 1].get())->transform; ssize_t index = transform_list.size() - 2; - ProgressBar progress ("composing linear transformations", transform_list.size()); + ProgressBar progress("composing linear transformations", transform_list.size()); progress++; while (index >= 0) { - composed = dynamic_cast(transform_list[index].get())->transform * composed; + composed = dynamic_cast(transform_list[index].get())->transform * composed; index--; progress++; } - File::Matrix::save_transform (composed, argument[argument.size() - 1]); + File::Matrix::save_transform(composed, argument[argument.size() - 1]); } else { - Header output_header (*template_header); + Header output_header(*template_header); output_header.ndim() = 4; output_header.size(3) = 3; output_header.datatype() = DataType::Float32; - Image output = Image::create (argument [argument.size() - 1], output_header); + Image output = Image::create(argument[argument.size() - 1], output_header); - Transform template_transform (output); - for (auto i = Loop ("composing transformations", output, 0, 3) (output); i ; ++i) { - Eigen::Vector3d voxel ((default_type) output.index(0), - (default_type) output.index(1), - (default_type) output.index(2)); + Transform template_transform(output); + for (auto i = Loop("composing transformations", output, 0, 3)(output); i; ++i) { + Eigen::Vector3d voxel( + (default_type)output.index(0), (default_type)output.index(1), (default_type)output.index(2)); Eigen::Vector3d position = template_transform.voxel2scanner * voxel; ssize_t index = transform_list.size() - 1; while (index >= 0) { - position = transform_list[index]->transform_point (position); + position = transform_list[index]->transform_point(position); index--; } output.row(3) = position; diff --git a/cmd/transformconvert.cpp b/cmd/transformconvert.cpp index 94f01992d9..46eb813be3 100644 --- a/cmd/transformconvert.cpp +++ b/cmd/transformconvert.cpp @@ -14,62 +14,56 @@ * For more details, see http://www.mrtrix.org/. */ -#include -#include #include "command.h" -#include "image.h" -#include "transform.h" #include "file/key_value.h" #include "file/matrix.h" #include "file/nifti_utils.h" +#include "image.h" +#include "transform.h" +#include +#include using namespace MR; using namespace App; -const char* operations[] = { - "flirt_import", - "itk_import", - NULL -}; +const char *operations[] = {"flirt_import", "itk_import", NULL}; -void usage () -{ +void usage() { AUTHOR = "Max Pietsch (maximilian.pietsch@kcl.ac.uk)"; SYNOPSIS = "Convert linear transformation matrices"; DESCRIPTION - + "This command allows to convert transformation matrices provided by other registration " - "softwares to a format usable in MRtrix3. Example usages are provided below."; + +"This command allows to convert transformation matrices provided by other registration " + "softwares to a format usable in MRtrix3. Example usages are provided below."; EXAMPLES - + Example ("Convert a transformation matrix produced by FSL's flirt command into a format usable by MRtrix3", - "transformconvert transform_flirt.mat flirt_in.nii flirt_ref.nii flirt_import transform_mrtrix.txt", - "The two images provided as inputs for this operation must be in the correct order: first the image " - "that was provided to flirt via the -in option, second the image that was provided to flirt via the " - "-ref option.") + +Example("Convert a transformation matrix produced by FSL's flirt command into a format usable by MRtrix3", + "transformconvert transform_flirt.mat flirt_in.nii flirt_ref.nii flirt_import transform_mrtrix.txt", + "The two images provided as inputs for this operation must be in the correct order: first the image " + "that was provided to flirt via the -in option, second the image that was provided to flirt via the " + "-ref option.") - + Example ("Convert a plain text transformation matrix file produced by ITK's affine registration " - "(e.g. ANTS, Slicer) into a format usable by MRtrix3", - "transformconvert transform_itk.txt itk_import transform_mrtrix.txt", - ""); + + Example("Convert a plain text transformation matrix file produced by ITK's affine registration " + "(e.g. ANTS, Slicer) into a format usable by MRtrix3", + "transformconvert transform_itk.txt itk_import transform_mrtrix.txt", + ""); ARGUMENTS - + Argument ("input", "the input(s) for the specified operation").allow_multiple() - + Argument ("operation", "the operation to perform, one of:\n" + join(operations, ", ")).type_choice (operations) - + Argument ("output", "the output transformation matrix.").type_file_out (); + +Argument("input", "the input(s) for the specified operation").allow_multiple() + + Argument("operation", "the operation to perform, one of:\n" + join(operations, ", ")).type_choice(operations) + + Argument("output", "the output transformation matrix.").type_file_out(); } - -transform_type get_flirt_transform (const Header& header) { +transform_type get_flirt_transform(const Header &header) { vector axes; - transform_type nifti_transform = File::NIfTI::adjust_transform (header, axes); - if (nifti_transform.matrix().topLeftCorner<3,3>().determinant() < 0.0) + transform_type nifti_transform = File::NIfTI::adjust_transform(header, axes); + if (nifti_transform.matrix().topLeftCorner<3, 3>().determinant() < 0.0) return nifti_transform; transform_type coord_switch; coord_switch.setIdentity(); - coord_switch(0,0) = -1.0f; - coord_switch(0,3) = (header.size(axes[0])-1) * header.spacing(axes[0]); + coord_switch(0, 0) = -1.0f; + coord_switch(0, 3) = (header.size(axes[0]) - 1) * header.spacing(axes[0]); return nifti_transform * coord_switch; } @@ -128,105 +122,103 @@ transform_type get_flirt_transform (const Header& header) { // } template -void parse_itk_trafo (const std::string& itk_file, TransformationType& transformation, Eigen::Vector3d& centre_of_rotation) { +void parse_itk_trafo(const std::string &itk_file, + TransformationType &transformation, + Eigen::Vector3d ¢re_of_rotation) { const std::string first_line = "#Insight Transform File V1.0"; vector supported_transformations = {"MatrixOffsetTransformBase_double_3_3", - "MatrixOffsetTransformBase_float_3_3", - "AffineTransform_double_3_3", - "AffineTransform_float_3_3" - }; + "MatrixOffsetTransformBase_float_3_3", + "AffineTransform_double_3_3", + "AffineTransform_float_3_3"}; // TODO, support derived classes that are compatible // FixedCenterOfRotationAffineTransform_float_3_3? // QuaternionRigidTransform_double_3_3? // QuaternionRigidTransform_float_3_3? - File::KeyValue::Reader file (itk_file, first_line.c_str()); + File::KeyValue::Reader file(itk_file, first_line.c_str()); std::string line; - size_t invalid (2); + size_t invalid(2); while (file.next()) { if (file.key() == "Transform") { - if (std::find(supported_transformations.begin(), supported_transformations.end(), file.value()) == supported_transformations.end()) - throw Exception ("The " + file.value() + " transform type is currenly not supported or tested"); - } - else if (file.key() == "Parameters") { + if (std::find(supported_transformations.begin(), supported_transformations.end(), file.value()) == + supported_transformations.end()) + throw Exception("The " + file.value() + " transform type is currenly not supported or tested"); + } else if (file.key() == "Parameters") { line = file.value(); - std::replace (line.begin(), line.end(), ' ', ','); - vector parameters (parse_floats (line)); + std::replace(line.begin(), line.end(), ' ', ','); + vector parameters(parse_floats(line)); if (parameters.size() != 12) - throw Exception ("Expected itk file with 12 parameters but has " + str(parameters.size()) + " parameters."); + throw Exception("Expected itk file with 12 parameters but has " + str(parameters.size()) + " parameters."); transformation.linear().row(0) << parameters[0], parameters[1], parameters[2]; transformation.linear().row(1) << parameters[3], parameters[4], parameters[5]; - transformation.linear().row(2) << parameters[6], parameters[7], parameters[8]; + transformation.linear().row(2) << parameters[6], parameters[7], parameters[8]; transformation.translation() << parameters[9], parameters[10], parameters[11]; invalid--; - } - else if (file.key() == "FixedParameters") { + } else if (file.key() == "FixedParameters") { line = file.value(); - std::replace (line.begin(), line.end(), ' ', ','); - vector fixed_parameters (parse_floats (line)); + std::replace(line.begin(), line.end(), ' ', ','); + vector fixed_parameters(parse_floats(line)); centre_of_rotation << fixed_parameters[0], fixed_parameters[1], fixed_parameters[2]; invalid--; } } file.close(); if (invalid != 0) - throw Exception ("ITK transformation could not be read"); + throw Exception("ITK transformation could not be read"); } -void run () -{ +void run() { const size_t num_inputs = argument.size() - 2; const int op = argument[num_inputs]; - const std::string& output_path = argument.back(); + const std::string &output_path = argument.back(); switch (op) { - case 0: { // flirt_import - if (num_inputs != 3) - throw Exception ("flirt_import requires 3 inputs"); - transform_type transform = File::Matrix::load_transform (argument[0]); - auto src_header = Header::open (argument[1]); // -in - auto dest_header = Header::open (argument[2]); // -ref - - if (transform.matrix().topLeftCorner<3,3>().determinant() == float(0.0)) - WARN ("Transformation matrix determinant is zero."); - if (transform.matrix().topLeftCorner<3,3>().determinant() < 0) - INFO ("Transformation matrix determinant is negative."); - - transform_type src_flirt_to_scanner = get_flirt_transform (src_header); - transform_type dest_flirt_to_scanner = get_flirt_transform (dest_header); - - transform_type forward_transform = dest_flirt_to_scanner * transform * src_flirt_to_scanner.inverse(); - if (((forward_transform.matrix().array() != forward_transform.matrix().array())).any()) - WARN ("NAN in transformation."); - File::Matrix::save_transform (forward_transform.inverse(), output_path); - break; - } - case 1: { // ITK import - if (num_inputs != 1) - throw Exception ("itk_import requires 1 input, " + str(num_inputs) + " provided."); + case 0: { // flirt_import + if (num_inputs != 3) + throw Exception("flirt_import requires 3 inputs"); + transform_type transform = File::Matrix::load_transform(argument[0]); + auto src_header = Header::open(argument[1]); // -in + auto dest_header = Header::open(argument[2]); // -ref + + if (transform.matrix().topLeftCorner<3, 3>().determinant() == float(0.0)) + WARN("Transformation matrix determinant is zero."); + if (transform.matrix().topLeftCorner<3, 3>().determinant() < 0) + INFO("Transformation matrix determinant is negative."); + + transform_type src_flirt_to_scanner = get_flirt_transform(src_header); + transform_type dest_flirt_to_scanner = get_flirt_transform(dest_header); + + transform_type forward_transform = dest_flirt_to_scanner * transform * src_flirt_to_scanner.inverse(); + if (((forward_transform.matrix().array() != forward_transform.matrix().array())).any()) + WARN("NAN in transformation."); + File::Matrix::save_transform(forward_transform.inverse(), output_path); + break; + } + case 1: { // ITK import + if (num_inputs != 1) + throw Exception("itk_import requires 1 input, " + str(num_inputs) + " provided."); - transform_type transform; - Eigen::Vector3d centre_of_rotation (3); - parse_itk_trafo (argument[0], transform, centre_of_rotation); - INFO("Centre of rotation:\n" + str(centre_of_rotation.transpose())); + transform_type transform; + Eigen::Vector3d centre_of_rotation(3); + parse_itk_trafo(argument[0], transform, centre_of_rotation); + INFO("Centre of rotation:\n" + str(centre_of_rotation.transpose())); - // rejig translation to correct for centre of rotation - transform.translation() = transform.translation() + centre_of_rotation - transform.linear() * centre_of_rotation; + // rejig translation to correct for centre of rotation + transform.translation() = transform.translation() + centre_of_rotation - transform.linear() * centre_of_rotation; - // TODO is the coordinate switch robust to large rotations? - transform.matrix().template block<2,2>(0,2) *= -1.0; - transform.matrix().template block<1,2>(2,0) *= -1.0; + // TODO is the coordinate switch robust to large rotations? + transform.matrix().template block<2, 2>(0, 2) *= -1.0; + transform.matrix().template block<1, 2>(2, 0) *= -1.0; - INFO("linear:\n" + str(transform.matrix())); - INFO("translation:\n" + str(transform.translation().transpose())); - if (((transform.matrix().array() != transform.matrix().array())).any()) - WARN ("NAN in transformation."); + INFO("linear:\n" + str(transform.matrix())); + INFO("translation:\n" + str(transform.translation().transpose())); + if (((transform.matrix().array() != transform.matrix().array())).any()) + WARN("NAN in transformation."); - File::Matrix::save_transform (transform, output_path); - break; - } - default: assert (0); + File::Matrix::save_transform(transform, output_path); + break; + } + default: + assert(0); } - - } diff --git a/cmd/tsfdivide.cpp b/cmd/tsfdivide.cpp index 8366cf4c76..7a68a08e21 100644 --- a/cmd/tsfdivide.cpp +++ b/cmd/tsfdivide.cpp @@ -19,58 +19,53 @@ #include "dwi/tractography/scalar_file.h" #include "dwi/tractography/streamline.h" - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Divide corresponding values in track scalar files"; ARGUMENTS - + Argument ("input1", "the first input track scalar file.").type_file_in() - + Argument ("input2", "the second input track scalar file.").type_file_in() - + Argument ("output", "the output track scalar file").type_file_out(); + +Argument("input1", "the first input track scalar file.").type_file_in() + + Argument("input2", "the second input track scalar file.").type_file_in() + + Argument("output", "the output track scalar file").type_file_out(); } using value_type = float; - -void run () -{ +void run() { DWI::Tractography::Properties properties1, properties2; - DWI::Tractography::ScalarReader reader1 (argument[0], properties1); - DWI::Tractography::ScalarReader reader2 (argument[1], properties2); - DWI::Tractography::check_properties_match (properties1, properties2, "scalar", false); + DWI::Tractography::ScalarReader reader1(argument[0], properties1); + DWI::Tractography::ScalarReader reader2(argument[1], properties2); + DWI::Tractography::check_properties_match(properties1, properties2, "scalar", false); - DWI::Tractography::ScalarWriter writer (argument[2], properties1); + DWI::Tractography::ScalarWriter writer(argument[2], properties1); DWI::Tractography::TrackScalar tck_scalar1, tck_scalar2, tck_scalar_output; - while (reader1 (tck_scalar1)) { - if (!reader2 (tck_scalar2)) { - WARN ("No more track scalars left in input file \"" + std::string(argument[1]) + - "\" after " + str(tck_scalar1.get_index()+1) + " streamlines; " + - "but more data are present in input file \"" + std::string(argument[0]) + "\""); + while (reader1(tck_scalar1)) { + if (!reader2(tck_scalar2)) { + WARN("No more track scalars left in input file \"" + std::string(argument[1]) + "\" after " + + str(tck_scalar1.get_index() + 1) + " streamlines; " + "but more data are present in input file \"" + + std::string(argument[0]) + "\""); break; } if (tck_scalar1.size() != tck_scalar2.size()) - throw Exception ("track scalar length mismatch at streamline index " + str(tck_scalar1.get_index())); + throw Exception("track scalar length mismatch at streamline index " + str(tck_scalar1.get_index())); - tck_scalar_output.set_index (tck_scalar1.get_index()); - tck_scalar_output.resize (tck_scalar1.size()); + tck_scalar_output.set_index(tck_scalar1.get_index()); + tck_scalar_output.resize(tck_scalar1.size()); for (size_t i = 0; i < tck_scalar1.size(); ++i) { if (tck_scalar2[i] == value_type(0)) tck_scalar_output[i] = value_type(0); else tck_scalar_output[i] = tck_scalar1[i] / tck_scalar2[i]; } - writer (tck_scalar_output); + writer(tck_scalar_output); } - if (reader2 (tck_scalar2)) { - WARN ("No more track scalars left in input file \"" + std::string(argument[0]) + - "\" after " + str(tck_scalar1.get_index()+1) + " streamlines; " + - "but more data are present in input file \"" + std::string(argument[1]) + "\""); + if (reader2(tck_scalar2)) { + WARN("No more track scalars left in input file \"" + std::string(argument[0]) + "\" after " + + str(tck_scalar1.get_index() + 1) + " streamlines; " + "but more data are present in input file \"" + + std::string(argument[1]) + "\""); } } - diff --git a/cmd/tsfinfo.cpp b/cmd/tsfinfo.cpp index 6828b7a676..fed18c7737 100644 --- a/cmd/tsfinfo.cpp +++ b/cmd/tsfinfo.cpp @@ -15,56 +15,48 @@ */ #include "command.h" -#include "progressbar.h" -#include "file/ofstream.h" #include "dwi/tractography/properties.h" #include "dwi/tractography/scalar_file.h" #include "dwi/tractography/streamline.h" +#include "file/ofstream.h" +#include "progressbar.h" using namespace MR; using namespace MR::DWI; using namespace App; -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Print out information about a track scalar file"; ARGUMENTS - + Argument ("tracks", "the input track scalar file.") - .allow_multiple() - .type_file_in(); + +Argument("tracks", "the input track scalar file.").allow_multiple().type_file_in(); OPTIONS - + Option ("count", - "count number of tracks in file explicitly, ignoring the header") + +Option("count", "count number of tracks in file explicitly, ignoring the header") - + Option ("ascii", - "save values of each track scalar file in individual ascii files, with the " - "specified prefix.") - + Argument ("prefix").type_text(); + + Option("ascii", + "save values of each track scalar file in individual ascii files, with the " + "specified prefix.") + + Argument("prefix").type_text(); } +void run() { - - -void run () -{ - - auto opt = get_options ("ascii"); - bool actual_count = get_options ("count").size(); + auto opt = get_options("ascii"); + bool actual_count = get_options("count").size(); for (size_t i = 0; i < argument.size(); ++i) { Tractography::Properties properties; - Tractography::ScalarReader file (argument[i], properties); + Tractography::ScalarReader file(argument[i], properties); std::cout << "***********************************\n"; std::cout << " Track scalar file: \"" << argument[i] << "\"\n"; for (Tractography::Properties::iterator i = properties.begin(); i != properties.end(); ++i) { - std::string S (i->first + ':'); - S.resize (22, ' '); + std::string S(i->first + ':'); + S.resize(22, ' '); std::cout << " " << S << i->second << "\n"; } @@ -74,17 +66,17 @@ void run () std::cout << (i == properties.comments.begin() ? "" : " ") << *i << "\n"; } - for (std::multimap::const_iterator i = properties.prior_rois.begin(); i != properties.prior_rois.end(); ++i) + for (std::multimap::const_iterator i = properties.prior_rois.begin(); + i != properties.prior_rois.end(); + ++i) std::cout << " ROI: " << i->first << " " << i->second << "\n"; - - if (actual_count) { DWI::Tractography::TrackScalar<> tck; size_t count = 0; { - ProgressBar progress ("counting tracks in file"); - while (file (tck)) { + ProgressBar progress("counting tracks in file"); + while (file(tck)) { ++count; ++progress; } @@ -93,15 +85,15 @@ void run () } if (opt.size()) { - ProgressBar progress ("writing track scalar data to ascii files"); + ProgressBar progress("writing track scalar data to ascii files"); DWI::Tractography::TrackScalar<> tck; - while (file (tck)) { - std::string filename (opt[0][0]); + while (file(tck)) { + std::string filename(opt[0][0]); filename += "-000000.txt"; - std::string num (str (tck.get_index())); - filename.replace (filename.size()-4-num.size(), num.size(), num); + std::string num(str(tck.get_index())); + filename.replace(filename.size() - 4 - num.size(), num.size(), num); - File::OFStream out (filename); + File::OFStream out(filename); for (vector::iterator i = tck.begin(); i != tck.end(); ++i) out << (*i) << "\n"; out.close(); diff --git a/cmd/tsfmult.cpp b/cmd/tsfmult.cpp index 5ef0caa932..fb814e7164 100644 --- a/cmd/tsfmult.cpp +++ b/cmd/tsfmult.cpp @@ -19,55 +19,50 @@ #include "dwi/tractography/scalar_file.h" #include "dwi/tractography/streamline.h" - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Multiply corresponding values in track scalar files"; ARGUMENTS - + Argument ("input1", "the first input track scalar file.").type_file_in() - + Argument ("input1", "the second input track scalar file.").type_file_in() - + Argument ("output", "the output track scalar file").type_file_out(); + +Argument("input1", "the first input track scalar file.").type_file_in() + + Argument("input1", "the second input track scalar file.").type_file_in() + + Argument("output", "the output track scalar file").type_file_out(); } using value_type = float; - -void run () -{ +void run() { DWI::Tractography::Properties properties1, properties2; - DWI::Tractography::ScalarReader reader1 (argument[0], properties1); - DWI::Tractography::ScalarReader reader2 (argument[1], properties2); - DWI::Tractography::check_properties_match (properties1, properties2, "scalar", false); + DWI::Tractography::ScalarReader reader1(argument[0], properties1); + DWI::Tractography::ScalarReader reader2(argument[1], properties2); + DWI::Tractography::check_properties_match(properties1, properties2, "scalar", false); - DWI::Tractography::ScalarWriter writer (argument[2], properties1); + DWI::Tractography::ScalarWriter writer(argument[2], properties1); DWI::Tractography::TrackScalar<> tck_scalar1, tck_scalar2, tck_scalar_output; - while (reader1 (tck_scalar1)) { - if (!reader2 (tck_scalar2)) { - WARN ("No more track scalars left in input file \"" + std::string(argument[1]) + - "\" after " + str(tck_scalar1.get_index()+1) + " streamlines; " + - "but more data are present in input file \"" + std::string(argument[0]) + "\""); + while (reader1(tck_scalar1)) { + if (!reader2(tck_scalar2)) { + WARN("No more track scalars left in input file \"" + std::string(argument[1]) + "\" after " + + str(tck_scalar1.get_index() + 1) + " streamlines; " + "but more data are present in input file \"" + + std::string(argument[0]) + "\""); break; } if (tck_scalar1.size() != tck_scalar2.size()) - throw Exception ("track scalar length mismatch at streamline index " + str(tck_scalar1.get_index())); + throw Exception("track scalar length mismatch at streamline index " + str(tck_scalar1.get_index())); - tck_scalar_output.set_index (tck_scalar1.get_index()); - tck_scalar_output.resize (tck_scalar1.size()); + tck_scalar_output.set_index(tck_scalar1.get_index()); + tck_scalar_output.resize(tck_scalar1.size()); for (size_t i = 0; i < tck_scalar1.size(); ++i) { tck_scalar_output[i] = tck_scalar1[i] * tck_scalar2[i]; } - writer (tck_scalar_output); + writer(tck_scalar_output); } - if (reader2 (tck_scalar2)) { - WARN ("No more track scalars left in input file \"" + std::string(argument[0]) + - "\" after " + str(tck_scalar1.get_index()+1) + " streamlines; " + - "but more data are present in input file \"" + std::string(argument[1]) + "\""); + if (reader2(tck_scalar2)) { + WARN("No more track scalars left in input file \"" + std::string(argument[0]) + "\" after " + + str(tck_scalar1.get_index() + 1) + " streamlines; " + "but more data are present in input file \"" + + std::string(argument[1]) + "\""); } } - diff --git a/cmd/tsfsmooth.cpp b/cmd/tsfsmooth.cpp index 50dfa231fd..07615f87ac 100644 --- a/cmd/tsfsmooth.cpp +++ b/cmd/tsfsmooth.cpp @@ -15,59 +15,56 @@ */ #include "command.h" -#include "math/median.h" #include "dwi/tractography/properties.h" #include "dwi/tractography/scalar_file.h" #include "dwi/tractography/streamline.h" - +#include "math/median.h" #define DEFAULT_SMOOTHING 4.0 - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Gaussian filter a track scalar file"; ARGUMENTS - + Argument ("input", "the input track scalar file.").type_file_in () - + Argument ("output", "the output track scalar file").type_file_out (); + +Argument("input", "the input track scalar file.").type_file_in() + + Argument("output", "the output track scalar file").type_file_out(); OPTIONS - + Option ("stdev", "apply Gaussian smoothing with the specified standard deviation. " - "The standard deviation is defined in units of track points (default: " + str(DEFAULT_SMOOTHING, 2) + ")") - + Argument ("sigma").type_float(1e-6); + +Option("stdev", + "apply Gaussian smoothing with the specified standard deviation. " + "The standard deviation is defined in units of track points (default: " + + str(DEFAULT_SMOOTHING, 2) + ")") + + Argument("sigma").type_float(1e-6); } using value_type = float; - -void run () -{ +void run() { DWI::Tractography::Properties properties; - DWI::Tractography::ScalarReader reader (argument[0], properties); - DWI::Tractography::ScalarWriter writer (argument[1], properties); + DWI::Tractography::ScalarReader reader(argument[0], properties); + DWI::Tractography::ScalarWriter writer(argument[1], properties); - float stdev = get_option_value ("stdev", DEFAULT_SMOOTHING); + float stdev = get_option_value("stdev", DEFAULT_SMOOTHING); - vector kernel (2 * ceil(2.5 * stdev) + 1, 0); + vector kernel(2 * ceil(2.5 * stdev) + 1, 0); float norm_factor = 0.0; float radius = (kernel.size() - 1.0) / 2.0; for (size_t c = 0; c < kernel.size(); ++c) { - kernel[c] = exp(-(c - radius) * (c - radius) / (2 * stdev * stdev)); + kernel[c] = exp(-(c - radius) * (c - radius) / (2 * stdev * stdev)); norm_factor += kernel[c]; } for (size_t c = 0; c < kernel.size(); c++) kernel[c] /= norm_factor; DWI::Tractography::TrackScalar tck_scalar; - while (reader (tck_scalar)) { - DWI::Tractography::TrackScalar tck_scalars_smoothed (tck_scalar.size()); - tck_scalars_smoothed.set_index (tck_scalar.get_index()); + while (reader(tck_scalar)) { + DWI::Tractography::TrackScalar tck_scalars_smoothed(tck_scalar.size()); + tck_scalars_smoothed.set_index(tck_scalar.get_index()); for (int i = 0; i < (int)tck_scalar.size(); ++i) { float norm_factor = 0.0; @@ -80,7 +77,6 @@ void run () } tck_scalars_smoothed[i] = value / norm_factor; } - writer (tck_scalars_smoothed); + writer(tck_scalars_smoothed); } } - diff --git a/cmd/tsfthreshold.cpp b/cmd/tsfthreshold.cpp index 13a142133a..89e530ef5a 100644 --- a/cmd/tsfthreshold.cpp +++ b/cmd/tsfthreshold.cpp @@ -19,43 +19,37 @@ #include "dwi/tractography/scalar_file.h" #include "dwi/tractography/streamline.h" - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Threshold and invert track scalar files"; ARGUMENTS - + Argument ("input", "the input track scalar file.").type_file_in() - + Argument ("T", "the desired threshold").type_float () - + Argument ("output", "the binary output track scalar file").type_file_out(); - + +Argument("input", "the input track scalar file.").type_file_in() + + Argument("T", "the desired threshold").type_float() + + Argument("output", "the binary output track scalar file").type_file_out(); OPTIONS - + Option ("invert", "invert the output mask"); - + +Option("invert", "invert the output mask"); } using value_type = float; - -void run () -{ +void run() { bool invert = get_options("invert").size() ? true : false; float threshold = argument[1]; DWI::Tractography::Properties properties; - DWI::Tractography::ScalarReader reader (argument[0], properties); - DWI::Tractography::ScalarWriter writer (argument[2], properties); + DWI::Tractography::ScalarReader reader(argument[0], properties); + DWI::Tractography::ScalarWriter writer(argument[2], properties); DWI::Tractography::TrackScalar tck_scalar; - while (reader (tck_scalar)) { - DWI::Tractography::TrackScalar tck_mask (tck_scalar.size()); - tck_mask.set_index (tck_scalar.get_index()); + while (reader(tck_scalar)) { + DWI::Tractography::TrackScalar tck_mask(tck_scalar.size()); + tck_mask.set_index(tck_scalar.get_index()); for (size_t i = 0; i < tck_scalar.size(); ++i) { if (invert) { if (tck_scalar[i] > threshold) @@ -69,7 +63,6 @@ void run () tck_mask[i] = value_type(0); } } - writer (tck_mask); + writer(tck_mask); } } - diff --git a/cmd/tsfvalidate.cpp b/cmd/tsfvalidate.cpp index dc8497d418..4ce974103b 100644 --- a/cmd/tsfvalidate.cpp +++ b/cmd/tsfvalidate.cpp @@ -15,61 +15,55 @@ */ #include "command.h" -#include "progressbar.h" -#include "types.h" #include "dwi/tractography/file.h" +#include "dwi/tractography/properties.h" #include "dwi/tractography/scalar_file.h" #include "dwi/tractography/streamline.h" -#include "dwi/tractography/properties.h" - +#include "progressbar.h" +#include "types.h" using namespace MR; using namespace MR::DWI::Tractography; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Validate a track scalar file against the corresponding track data"; ARGUMENTS - + Argument ("tsf", "the input track scalar file").type_file_in() - + Argument ("tracks", "the track file on which the TSF is based").type_file_in(); + +Argument("tsf", "the input track scalar file").type_file_in() + + Argument("tracks", "the track file on which the TSF is based").type_file_in(); } - typedef float value_type; - -void run () -{ +void run() { Properties tsf_properties, tck_properties; - ScalarReader tsf_reader (argument[0], tsf_properties); - Reader tck_reader (argument[1], tck_properties); + ScalarReader tsf_reader(argument[0], tsf_properties); + Reader tck_reader(argument[1], tck_properties); size_t error_count = 0; - Properties::const_iterator tsf_count_field = tsf_properties.find ("count"); - Properties::const_iterator tck_count_field = tck_properties.find ("count"); + Properties::const_iterator tsf_count_field = tsf_properties.find("count"); + Properties::const_iterator tck_count_field = tck_properties.find("count"); size_t tsf_header_count = 0, tck_header_count = 0; if (tsf_count_field == tsf_properties.end() || tck_count_field == tck_properties.end()) { - WARN ("Unable to verify equal track counts: \"count\" field absent from file header"); + WARN("Unable to verify equal track counts: \"count\" field absent from file header"); } else { - tsf_header_count = to (tsf_count_field->second); - tck_header_count = to (tck_count_field->second); + tsf_header_count = to(tsf_count_field->second); + tck_header_count = to(tck_count_field->second); if (tsf_header_count != tck_header_count) { - CONSOLE ("\"count\" fields in file headers do not match"); + CONSOLE("\"count\" fields in file headers do not match"); ++error_count; } } - Properties::const_iterator tsf_timestamp_field = tsf_properties.find ("timestamp"); - Properties::const_iterator tck_timestamp_field = tck_properties.find ("timestamp"); + Properties::const_iterator tsf_timestamp_field = tsf_properties.find("timestamp"); + Properties::const_iterator tck_timestamp_field = tck_properties.find("timestamp"); if (tsf_timestamp_field == tsf_properties.end() || tck_timestamp_field == tck_properties.end()) { - WARN ("Unable to verify equal file timestamps: \"timestamp\" field absent from file header"); + WARN("Unable to verify equal file timestamps: \"timestamp\" field absent from file header"); } else if (tsf_timestamp_field->second != tck_timestamp_field->second) { - CONSOLE ("\"timestamp\" fields in file headers do not match"); + CONSOLE("\"timestamp\" fields in file headers do not match"); ++error_count; } @@ -78,10 +72,10 @@ void run () size_t tck_counter = 0, tsf_counter = 0, length_mismatch_count = 0; { - ProgressBar progress ("Validating track scalar file", tck_header_count); - while (tck_reader (track)) { + ProgressBar progress("Validating track scalar file", tck_header_count); + while (tck_reader(track)) { ++tck_counter; - if (tsf_reader (scalar)) { + if (tsf_reader(scalar)) { ++tsf_counter; if (track.size() != scalar.size()) ++length_mismatch_count; @@ -89,33 +83,37 @@ void run () ++progress; } - while (tsf_reader (scalar)) { + while (tsf_reader(scalar)) { ++tsf_counter; ++progress; } } if (tsf_header_count && tsf_counter != tsf_header_count) { - CONSOLE ("Actual number of tracks counted in scalar file (" + str(tsf_counter) + ") does not match number reported in header (" + str(tsf_header_count) + ")"); + CONSOLE("Actual number of tracks counted in scalar file (" + str(tsf_counter) + + ") does not match number reported in header (" + str(tsf_header_count) + ")"); ++error_count; } if (tck_header_count && tck_counter != tck_header_count) { - CONSOLE ("Actual number of tracks counted in track file (" + str(tck_counter) + ") does not match number reported in header (" + str(tck_header_count) + ")"); + CONSOLE("Actual number of tracks counted in track file (" + str(tck_counter) + + ") does not match number reported in header (" + str(tck_header_count) + ")"); ++error_count; } if (tck_counter != tsf_counter) { - CONSOLE ("Actual number of tracks counter in scalar file (" + str(tsf_counter) + ") does not match actual number of tracks counted in track file (" + str(tck_counter) + ")"); + CONSOLE("Actual number of tracks counter in scalar file (" + str(tsf_counter) + + ") does not match actual number of tracks counted in track file (" + str(tck_counter) + ")"); ++error_count; } if (length_mismatch_count) { - CONSOLE (str(length_mismatch_count) + " track" + (length_mismatch_count == 1 ? " was" : "s were") + " detected with different lengths between track and scalar data"); + CONSOLE(str(length_mismatch_count) + " track" + (length_mismatch_count == 1 ? " was" : "s were") + + " detected with different lengths between track and scalar data"); ++error_count; } if (error_count > 1) { - throw Exception ("Multiple errors detected"); + throw Exception("Multiple errors detected"); } else if (error_count) { - throw Exception ("Error detected"); + throw Exception("Error detected"); } else { - CONSOLE ("Track scalar file data checked OK"); + CONSOLE("Track scalar file data checked OK"); } } diff --git a/cmd/vectorstats.cpp b/cmd/vectorstats.cpp index ea28c85b65..a2b3567382 100644 --- a/cmd/vectorstats.cpp +++ b/cmd/vectorstats.cpp @@ -28,7 +28,6 @@ #include "stats/permtest.h" - using namespace MR; using namespace App; using namespace MR::Math::Stats; @@ -36,48 +35,39 @@ using namespace MR::Math::Stats::GLM; using MR::Math::Stats::index_type; - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Statistical testing of vector data using non-parametric permutation testing"; DESCRIPTION - + "This command can be used to perform permutation testing of any form of data. " - "The data for each input subject must be stored in a text file, with one value per row. " - "The data for each row across subjects will be tested independently, i.e. there is no " - "statistical enhancement that occurs between the data; however family-wise error control " - "will be used." - - + Math::Stats::GLM::column_ones_description; + +"This command can be used to perform permutation testing of any form of data. " + "The data for each input subject must be stored in a text file, with one value per row. " + "The data for each row across subjects will be tested independently, i.e. there is no " + "statistical enhancement that occurs between the data; however family-wise error control " + "will be used." + + Math::Stats::GLM::column_ones_description; ARGUMENTS - + Argument ("input", "a text file listing the file names of the input subject data").type_file_in () + +Argument("input", "a text file listing the file names of the input subject data").type_file_in() - + Argument ("design", "the design matrix").type_file_in () + + Argument("design", "the design matrix").type_file_in() - + Argument ("contrast", "the contrast matrix").type_file_in () - - + Argument ("output", "the filename prefix for all output").type_text(); + + Argument("contrast", "the contrast matrix").type_file_in() + + Argument("output", "the filename prefix for all output").type_text(); OPTIONS - + Math::Stats::shuffle_options (false) - - + Math::Stats::GLM::glm_options ("element"); + +Math::Stats::shuffle_options(false) + + Math::Stats::GLM::glm_options("element"); } - - using Math::Stats::matrix_type; using Math::Stats::vector_type; using Stats::PermTest::count_matrix_type; - - // Define data importer class that willl obtain data for a // specific subject based on the string path to the data file for // that subject @@ -86,36 +76,27 @@ using Stats::PermTest::count_matrix_type; // MRtrix3 statistical inference commands, since the data are // already in a vectorised form. -class SubjectVectorImport : public SubjectDataImportBase -{ - public: - SubjectVectorImport (const std::string& path) : - SubjectDataImportBase (path), - data (File::Matrix::load_vector (path)) { } - - void operator() (matrix_type::RowXpr row) const override - { - assert (index_type(row.size()) == size()); - row = data; - } +class SubjectVectorImport : public SubjectDataImportBase { +public: + SubjectVectorImport(const std::string &path) : SubjectDataImportBase(path), data(File::Matrix::load_vector(path)) {} - default_type operator[] (const index_type index) const override - { - assert (index < size()); - return data[index]; - } + void operator()(matrix_type::RowXpr row) const override { + assert(index_type(row.size()) == size()); + row = data; + } - index_type size() const override { return data.size(); } + default_type operator[](const index_type index) const override { + assert(index < size()); + return data[index]; + } - private: - const vector_type data; + index_type size() const override { return data.size(); } +private: + const vector_type data; }; - - -void run() -{ +void run() { // Unlike other statistical inference commands, don't delay actual // loading of input data: feasible for the input itself to be @@ -125,150 +106,158 @@ void run() matrix_type data; index_type num_inputs = 0, num_elements = 0; try { - importer.initialise (argument[0]); + importer.initialise(argument[0]); num_inputs = importer.size(); num_elements = importer[0]->size(); for (index_type i = 0; i != importer.size(); ++i) { if (importer[i]->size() != num_elements) - throw Exception ("Subject file \"" + importer[i]->name() + "\" contains incorrect number of elements (" + str(importer[i]) + "; expected " + str(num_elements) + ")"); + throw Exception("Subject file \"" + importer[i]->name() + "\" contains incorrect number of elements (" + + str(importer[i]) + "; expected " + str(num_elements) + ")"); } - data.resize (num_inputs, num_elements); + data.resize(num_inputs, num_elements); for (index_type subject = 0; subject != num_inputs; subject++) - (*importer[subject]) (data.row(subject)); - } catch (Exception& e_asfilelist) { + (*importer[subject])(data.row(subject)); + } catch (Exception &e_asfilelist) { try { - data = File::Matrix::load_matrix (argument[0]); + data = File::Matrix::load_matrix(argument[0]); num_inputs = data.rows(); num_elements = data.cols(); - } catch (Exception& e_asmatrix) { - Exception e ("Unable to load input data from file \"" + argument[0] + '"'); - e.push_back ("Error when interpreted as containing list of file names: "); - e.push_back (e_asfilelist); - e.push_back ("Error when interpreted as numerical matrix data: "); - e.push_back (e_asmatrix); + } catch (Exception &e_asmatrix) { + Exception e("Unable to load input data from file \"" + argument[0] + '"'); + e.push_back("Error when interpreted as containing list of file names: "); + e.push_back(e_asfilelist); + e.push_back("Error when interpreted as numerical matrix data: "); + e.push_back(e_asmatrix); throw e; } } - CONSOLE ("Number of subjects: " + str(num_inputs)); - CONSOLE ("Number of elements: " + str(num_elements)); + CONSOLE("Number of subjects: " + str(num_inputs)); + CONSOLE("Number of elements: " + str(num_elements)); // Load design matrix - const matrix_type design = File::Matrix::load_matrix (argument[1]); + const matrix_type design = File::Matrix::load_matrix(argument[1]); if (index_type(design.rows()) != num_inputs) - throw Exception ("Number of subjects (" + str(num_inputs) + ") does not match number of rows in design matrix (" + str(design.rows()) + ")"); + throw Exception("Number of subjects (" + str(num_inputs) + ") does not match number of rows in design matrix (" + + str(design.rows()) + ")"); // Before validating the contrast matrix, we first need to see if there are any // additional design matrix columns coming from element-wise subject data vector extra_columns; bool nans_in_columns = false; - auto opt = get_options ("column"); + auto opt = get_options("column"); for (size_t i = 0; i != opt.size(); ++i) { - extra_columns.push_back (CohortDataImport()); - extra_columns[i].initialise (opt[i][0]); + extra_columns.push_back(CohortDataImport()); + extra_columns[i].initialise(opt[i][0]); if (!extra_columns[i].allFinite()) nans_in_columns = true; } const index_type num_factors = design.cols() + extra_columns.size(); - CONSOLE ("Number of factors: " + str(num_factors)); + CONSOLE("Number of factors: " + str(num_factors)); if (extra_columns.size()) { - CONSOLE ("Number of element-wise design matrix columns: " + str(extra_columns.size())); + CONSOLE("Number of element-wise design matrix columns: " + str(extra_columns.size())); if (nans_in_columns) - CONSOLE ("Non-finite values detected in element-wise design matrix columns; individual rows will be removed from voxel-wise design matrices accordingly"); + CONSOLE("Non-finite values detected in element-wise design matrix columns; individual rows will be removed from " + "voxel-wise design matrices accordingly"); } - check_design (design, extra_columns.size()); + check_design(design, extra_columns.size()); // Load variance groups - auto variance_groups = GLM::load_variance_groups (num_inputs); - const index_type num_vgs = variance_groups.size() ? variance_groups.maxCoeff()+1 : 1; + auto variance_groups = GLM::load_variance_groups(num_inputs); + const index_type num_vgs = variance_groups.size() ? variance_groups.maxCoeff() + 1 : 1; if (num_vgs > 1) - CONSOLE ("Number of variance groups: " + str(num_vgs)); + CONSOLE("Number of variance groups: " + str(num_vgs)); // Load hypotheses - const vector hypotheses = Math::Stats::GLM::load_hypotheses (argument[2]); + const vector hypotheses = Math::Stats::GLM::load_hypotheses(argument[2]); const index_type num_hypotheses = hypotheses.size(); if (hypotheses[0].cols() != num_factors) - throw Exception ("The number of columns in the contrast matrix (" + str(hypotheses[0].cols()) + ")" - + " does not equal the number of columns in the design matrix (" + str(design.cols()) + ")" - + (extra_columns.size() ? " (taking into account the " + str(extra_columns.size()) + " uses of -column)" : "")); - CONSOLE ("Number of hypotheses: " + str(num_hypotheses)); + throw Exception( + "The number of columns in the contrast matrix (" + str(hypotheses[0].cols()) + ")" + + " does not equal the number of columns in the design matrix (" + str(design.cols()) + ")" + + (extra_columns.size() ? " (taking into account the " + str(extra_columns.size()) + " uses of -column)" : "")); + CONSOLE("Number of hypotheses: " + str(num_hypotheses)); const std::string output_prefix = argument[3]; const bool nans_in_data = !data.allFinite(); if (nans_in_data) { - INFO ("Non-finite values present in data; rows will be removed from element-wise design matrices accordingly"); + INFO("Non-finite values present in data; rows will be removed from element-wise design matrices accordingly"); if (!extra_columns.size()) { - INFO ("(Note that this will result in slower execution than if such values were not present)"); + INFO("(Note that this will result in slower execution than if such values were not present)"); } } // Only add contrast matrix row number to image outputs if there's more than one hypothesis - auto postfix = [&] (const index_type i) { return (num_hypotheses > 1) ? ("_" + hypotheses[i].name()) : ""; }; + auto postfix = [&](const index_type i) { return (num_hypotheses > 1) ? ("_" + hypotheses[i].name()) : ""; }; { - matrix_type betas (num_factors, num_elements); - matrix_type abs_effect_size (num_elements, num_hypotheses); - matrix_type std_effect_size (num_elements, num_hypotheses); - matrix_type stdev (num_vgs, num_elements); - vector_type cond (num_elements); - - Math::Stats::GLM::all_stats (data, design, extra_columns, hypotheses, variance_groups, - cond, betas, abs_effect_size, std_effect_size, stdev); - - ProgressBar progress ("Outputting beta coefficients, effect size and standard deviation", 2 + (2 * num_hypotheses) + (nans_in_data || extra_columns.size() ? 1 : 0)); - File::Matrix::save_matrix (betas, output_prefix + "betas.csv"); + matrix_type betas(num_factors, num_elements); + matrix_type abs_effect_size(num_elements, num_hypotheses); + matrix_type std_effect_size(num_elements, num_hypotheses); + matrix_type stdev(num_vgs, num_elements); + vector_type cond(num_elements); + + Math::Stats::GLM::all_stats( + data, design, extra_columns, hypotheses, variance_groups, cond, betas, abs_effect_size, std_effect_size, stdev); + + ProgressBar progress("Outputting beta coefficients, effect size and standard deviation", + 2 + (2 * num_hypotheses) + (nans_in_data || extra_columns.size() ? 1 : 0)); + File::Matrix::save_matrix(betas, output_prefix + "betas.csv"); ++progress; for (index_type i = 0; i != num_hypotheses; ++i) { if (!hypotheses[i].is_F()) { - File::Matrix::save_vector (abs_effect_size.col(i), output_prefix + "abs_effect" + postfix(i) + ".csv"); + File::Matrix::save_vector(abs_effect_size.col(i), output_prefix + "abs_effect" + postfix(i) + ".csv"); ++progress; if (num_vgs == 1) - File::Matrix::save_vector (std_effect_size.col(i), output_prefix + "std_effect" + postfix(i) + ".csv"); + File::Matrix::save_vector(std_effect_size.col(i), output_prefix + "std_effect" + postfix(i) + ".csv"); } else { ++progress; } ++progress; } if (nans_in_data || extra_columns.size()) { - File::Matrix::save_vector (cond, output_prefix + "cond.csv"); + File::Matrix::save_vector(cond, output_prefix + "cond.csv"); ++progress; } if (num_vgs == 1) - File::Matrix::save_vector (stdev.row(0), output_prefix + "std_dev.csv"); + File::Matrix::save_vector(stdev.row(0), output_prefix + "std_dev.csv"); else - File::Matrix::save_matrix (stdev, output_prefix + "std_dev.csv"); + File::Matrix::save_matrix(stdev, output_prefix + "std_dev.csv"); } // Construct the class for performing the initial statistical tests std::shared_ptr glm_test; if (extra_columns.size() || nans_in_data) { if (variance_groups.size()) - glm_test.reset (new GLM::TestVariableHeteroscedastic (extra_columns, data, design, hypotheses, variance_groups, nans_in_data, nans_in_columns)); + glm_test.reset(new GLM::TestVariableHeteroscedastic( + extra_columns, data, design, hypotheses, variance_groups, nans_in_data, nans_in_columns)); else - glm_test.reset (new GLM::TestVariableHomoscedastic (extra_columns, data, design, hypotheses, nans_in_data, nans_in_columns)); + glm_test.reset( + new GLM::TestVariableHomoscedastic(extra_columns, data, design, hypotheses, nans_in_data, nans_in_columns)); } else { if (variance_groups.size()) - glm_test.reset (new GLM::TestFixedHeteroscedastic (data, design, hypotheses, variance_groups)); + glm_test.reset(new GLM::TestFixedHeteroscedastic(data, design, hypotheses, variance_groups)); else - glm_test.reset (new GLM::TestFixedHomoscedastic (data, design, hypotheses)); + glm_test.reset(new GLM::TestFixedHomoscedastic(data, design, hypotheses)); } // Precompute default statistic // Don't use convenience function: No enhancer! // Manually construct default shuffling matrix // TODO Change to use convenience function; we make an empty enhancer later anyway - const matrix_type default_shuffle (matrix_type::Identity (num_inputs, num_inputs)); + const matrix_type default_shuffle(matrix_type::Identity(num_inputs, num_inputs)); matrix_type default_statistic, default_zstat; - (*glm_test) (default_shuffle, default_statistic, default_zstat); + (*glm_test)(default_shuffle, default_statistic, default_zstat); for (index_type i = 0; i != num_hypotheses; ++i) { - File::Matrix::save_matrix (default_statistic.col(i), output_prefix + (hypotheses[i].is_F() ? "F" : "t") + "value" + postfix(i) + ".csv"); - File::Matrix::save_matrix (default_zstat.col(i), output_prefix + "Zstat" + postfix(i) + ".csv"); + File::Matrix::save_matrix(default_statistic.col(i), + output_prefix + (hypotheses[i].is_F() ? "F" : "t") + "value" + postfix(i) + ".csv"); + File::Matrix::save_matrix(default_zstat.col(i), output_prefix + "Zstat" + postfix(i) + ".csv"); } // Perform permutation testing - if (!get_options ("notest").size()) { + if (!get_options("notest").size()) { - const bool fwe_strong = get_options ("strong").size(); + const bool fwe_strong = get_options("strong").size(); if (fwe_strong && num_hypotheses == 1) { WARN("Option -strong has no effect when testing a single hypothesis only"); } @@ -277,20 +266,26 @@ void run() matrix_type null_distribution, uncorrected_pvalues; count_matrix_type null_contributions; matrix_type empirical_distribution; // unused - Stats::PermTest::run_permutations (glm_test, enhancer, empirical_distribution, default_zstat, fwe_strong, - null_distribution, null_contributions, uncorrected_pvalues); + Stats::PermTest::run_permutations(glm_test, + enhancer, + empirical_distribution, + default_zstat, + fwe_strong, + null_distribution, + null_contributions, + uncorrected_pvalues); if (fwe_strong) { - File::Matrix::save_vector (null_distribution.col(0), output_prefix + "null_dist.csv"); + File::Matrix::save_vector(null_distribution.col(0), output_prefix + "null_dist.csv"); } else { for (index_type i = 0; i != num_hypotheses; ++i) - File::Matrix::save_vector (null_distribution.col(i), output_prefix + "null_dist" + postfix(i) + ".csv"); + File::Matrix::save_vector(null_distribution.col(i), output_prefix + "null_dist" + postfix(i) + ".csv"); } - const matrix_type fwe_pvalues = MR::Math::Stats::fwe_pvalue (null_distribution, default_zstat); + const matrix_type fwe_pvalues = MR::Math::Stats::fwe_pvalue(null_distribution, default_zstat); for (index_type i = 0; i != num_hypotheses; ++i) { - File::Matrix::save_vector (fwe_pvalues.col(i), output_prefix + "fwe_1mpvalue" + postfix(i) + ".csv"); - File::Matrix::save_vector (uncorrected_pvalues.col(i), output_prefix + "uncorrected_1mpvalue" + postfix(i) + ".csv"); - File::Matrix::save_vector (null_contributions.col(i), output_prefix + "null_contributions" + postfix(i) + ".csv"); + File::Matrix::save_vector(fwe_pvalues.col(i), output_prefix + "fwe_1mpvalue" + postfix(i) + ".csv"); + File::Matrix::save_vector(uncorrected_pvalues.col(i), + output_prefix + "uncorrected_1mpvalue" + postfix(i) + ".csv"); + File::Matrix::save_vector(null_contributions.col(i), output_prefix + "null_contributions" + postfix(i) + ".csv"); } - } } diff --git a/cmd/voxel2fixel.cpp b/cmd/voxel2fixel.cpp index 8a68303bfe..9e1abf6269 100644 --- a/cmd/voxel2fixel.cpp +++ b/cmd/voxel2fixel.cpp @@ -14,10 +14,10 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "progressbar.h" #include "algo/loop.h" +#include "command.h" #include "image.h" +#include "progressbar.h" #include "fixel/fixel.h" #include "fixel/helpers.h" @@ -28,45 +28,46 @@ using namespace App; using Fixel::index_type; - -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Map the scalar value in each voxel to all fixels within that voxel"; DESCRIPTION - + "This command is designed to enable CFE-based statistical analysis to be performed on voxel-wise measures." - + Fixel::format_description; + +"This command is designed to enable CFE-based statistical analysis to be performed on voxel-wise measures." + + Fixel::format_description; ARGUMENTS - + Argument ("image_in", "the input image.").type_image_in() - + Argument ("fixel_directory_in", "the input fixel directory. Used to define the fixels and their directions").type_directory_in() - + Argument ("fixel_directory_out", "the fixel directory where the output will be written. This can be the same as the input directory if desired").type_text() - + Argument ("fixel_data_out", "the name of the fixel data image.").type_text(); + +Argument("image_in", "the input image.").type_image_in() + + Argument("fixel_directory_in", "the input fixel directory. Used to define the fixels and their directions") + .type_directory_in() + + Argument("fixel_directory_out", + "the fixel directory where the output will be written. This can be the same as the input directory if " + "desired") + .type_text() + + Argument("fixel_data_out", "the name of the fixel data image.").type_text(); } - -void run () -{ - auto scalar = Image::open (argument[0]); +void run() { + auto scalar = Image::open(argument[0]); std::string input_fixel_directory = argument[1]; - Fixel::check_fixel_directory (input_fixel_directory); - auto input_fixel_index = Fixel::find_index_header (input_fixel_directory).get_image(); - check_dimensions (scalar, input_fixel_index, 0, 3); + Fixel::check_fixel_directory(input_fixel_directory); + auto input_fixel_index = Fixel::find_index_header(input_fixel_directory).get_image(); + check_dimensions(scalar, input_fixel_index, 0, 3); std::string output_fixel_directory = argument[2]; if (input_fixel_directory != output_fixel_directory) { - ProgressBar progress ("copying fixel index and directions file into output directory"); + ProgressBar progress("copying fixel index and directions file into output directory"); progress++; - Fixel::copy_index_and_directions_file (input_fixel_directory, output_fixel_directory); + Fixel::copy_index_and_directions_file(input_fixel_directory, output_fixel_directory); progress++; } - auto output_fixel_data = Image::create (Path::join(output_fixel_directory, argument[3]), Fixel::data_header_from_index (input_fixel_index)); + auto output_fixel_data = Image::create(Path::join(output_fixel_directory, argument[3]), + Fixel::data_header_from_index(input_fixel_index)); - for (auto v = Loop ("mapping voxel scalar values to fixels", 0, 3)(scalar, input_fixel_index); v; ++v) { - for (auto f = Fixel::Loop (input_fixel_index) (output_fixel_data); f; ++f) + for (auto v = Loop("mapping voxel scalar values to fixels", 0, 3)(scalar, input_fixel_index); v; ++v) { + for (auto f = Fixel::Loop(input_fixel_index)(output_fixel_data); f; ++f) output_fixel_data.value() = scalar.value(); } } diff --git a/cmd/voxel2mesh.cpp b/cmd/voxel2mesh.cpp index f87983087b..fc2c7e6d38 100644 --- a/cmd/voxel2mesh.cpp +++ b/cmd/voxel2mesh.cpp @@ -16,66 +16,54 @@ #include "command.h" -#include "image.h" #include "filter/optimal_threshold.h" -#include "surface/mesh.h" +#include "image.h" #include "surface/algo/image2mesh.h" - - +#include "surface/mesh.h" using namespace MR; using namespace App; - - - - -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au)"; SYNOPSIS = "Generate a surface mesh representation from a voxel image"; DESCRIPTION + - "This command utilises the Marching Cubes algorithm to generate a polygonal surface " - "that represents the isocontour(s) of the input image at a particular intensity. By default, " - "an appropriate threshold will be determined automatically from the input image, however " - "the intensity value of the isocontour(s) can instead be set manually using the -threhsold " - "option." + "This command utilises the Marching Cubes algorithm to generate a polygonal surface " + "that represents the isocontour(s) of the input image at a particular intensity. By default, " + "an appropriate threshold will be determined automatically from the input image, however " + "the intensity value of the isocontour(s) can instead be set manually using the -threhsold " + "option." - + "If the -blocky option is used, then the Marching Cubes algorithm will not be used. " - "Instead, the input image will be interpreted as a binary mask image, and polygonal " - "surfaces will be generated at the outer faces of the voxel clusters within the mask."; + + "If the -blocky option is used, then the Marching Cubes algorithm will not be used. " + "Instead, the input image will be interpreted as a binary mask image, and polygonal " + "surfaces will be generated at the outer faces of the voxel clusters within the mask."; ARGUMENTS - + Argument ("input", "the input image.").type_image_in () - + Argument ("output", "the output mesh file.").type_file_out (); + +Argument("input", "the input image.").type_image_in() + Argument("output", "the output mesh file.").type_file_out(); OPTIONS - + Option ("blocky", "generate a \'blocky\' mesh that precisely represents the voxel edges") + +Option("blocky", "generate a \'blocky\' mesh that precisely represents the voxel edges") - + Option ("threshold", "manually set the intensity threshold for the Marching Cubes algorithm") - + Argument ("value").type_float(); + + Option("threshold", "manually set the intensity threshold for the Marching Cubes algorithm") + + Argument("value").type_float(); } - -void run () -{ +void run() { Surface::Mesh mesh; - if (get_options ("blocky").size()) { + if (get_options("blocky").size()) { - auto input = Image::open (argument[0]); - Surface::Algo::image2mesh_blocky (input, mesh); + auto input = Image::open(argument[0]); + Surface::Algo::image2mesh_blocky(input, mesh); } else { - auto input = Image::open (argument[0]); - const default_type threshold = get_option_value ("threshold", Filter::estimate_optimal_threshold (input)); - Surface::Algo::image2mesh_mc (input, mesh, threshold); - + auto input = Image::open(argument[0]); + const default_type threshold = get_option_value("threshold", Filter::estimate_optimal_threshold(input)); + Surface::Algo::image2mesh_mc(input, mesh, threshold); } - mesh.save (argument[1]); - + mesh.save(argument[1]); } diff --git a/cmd/warp2metric.cpp b/cmd/warp2metric.cpp index 95d6421223..ead251bf51 100644 --- a/cmd/warp2metric.cpp +++ b/cmd/warp2metric.cpp @@ -14,61 +14,57 @@ * For more details, see http://www.mrtrix.org/. */ -#include "command.h" -#include "image.h" -#include "algo/threaded_loop.h" #include "adapter/jacobian.h" -#include "registration/warp/helpers.h" +#include "algo/threaded_loop.h" +#include "command.h" #include "fixel/fixel.h" #include "fixel/helpers.h" #include "fixel/loop.h" +#include "image.h" +#include "registration/warp/helpers.h" using namespace MR; using namespace App; - -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Compute fixel-wise or voxel-wise metrics from a 4D deformation field"; DESCRIPTION - + Fixel::format_description; + +Fixel::format_description; REFERENCES - + "Raffelt, D.; Tournier, JD/; Smith, RE.; Vaughan, DN.; Jackson, G.; Ridgway, GR. Connelly, A." // Internal - "Investigating White Matter Fibre Density and Morphology using Fixel-Based Analysis. " - "Neuroimage, 2017, 144, 58-73, doi: 10.1016/j.neuroimage.2016.09.029"; + +"Raffelt, D.; Tournier, JD/; Smith, RE.; Vaughan, DN.; Jackson, G.; Ridgway, GR. Connelly, A." // Internal + "Investigating White Matter Fibre Density and Morphology using Fixel-Based Analysis. " + "Neuroimage, 2017, 144, 58-73, doi: 10.1016/j.neuroimage.2016.09.029"; ARGUMENTS - + Argument ("in", "the input deformation field").type_image_in(); + +Argument("in", "the input deformation field").type_image_in(); OPTIONS - + Option ("fc", "use an input template fixel image to define fibre orientations and output " - "a fixel image describing the change in fibre cross-section (FC) in the perpendicular " - "plane to the fixel orientation. e.g. warp2metric warp.mif -fc fixel_template_directory output_fixel_directory fc.mif") - + Argument ("template_fixel_directory").type_image_in() - + Argument ("output_fixel_directory").type_text() - + Argument ("output_fixel_data").type_text() - - + Option ("jmat", "output a Jacobian matrix image stored in column-major order along the 4th dimension." - "Note the output jacobian describes the warp gradient w.r.t the scanner space coordinate system") - + Argument ("output").type_image_out() - - + Option ("jdet", "output the Jacobian determinant instead of the full matrix") - + Argument ("output").type_image_out(); - + +Option("fc", + "use an input template fixel image to define fibre orientations and output " + "a fixel image describing the change in fibre cross-section (FC) in the perpendicular " + "plane to the fixel orientation. e.g. warp2metric warp.mif -fc fixel_template_directory " + "output_fixel_directory fc.mif") + + Argument("template_fixel_directory").type_image_in() + Argument("output_fixel_directory").type_text() + + Argument("output_fixel_data").type_text() + + + Option("jmat", + "output a Jacobian matrix image stored in column-major order along the 4th dimension." + "Note the output jacobian describes the warp gradient w.r.t the scanner space coordinate system") + + Argument("output").type_image_out() + + + Option("jdet", "output the Jacobian determinant instead of the full matrix") + + Argument("output").type_image_out(); } - using value_type = float; - -void run () -{ - auto input = Image::open (argument[0]).with_direct_io (3); - Registration::Warp::check_warp (input); +void run() { + auto input = Image::open(argument[0]).with_direct_io(3); + Registration::Warp::check_warp(input); Image jmatrix_output; Image jdeterminant_output; @@ -77,47 +73,48 @@ void run () Image fixel_template_directions; Image fc_output_data; - auto opt = get_options ("fc"); + auto opt = get_options("fc"); if (opt.size()) { - std::string template_fixel_directory (opt[0][0]); - fixel_template_index = Fixel::find_index_header (template_fixel_directory).get_image(); - fixel_template_directions = Fixel::find_directions_header (template_fixel_directory).get_image().with_direct_io(); + std::string template_fixel_directory(opt[0][0]); + fixel_template_index = Fixel::find_index_header(template_fixel_directory).get_image(); + fixel_template_directions = + Fixel::find_directions_header(template_fixel_directory).get_image().with_direct_io(); - std::string output_fixel_directory (opt[0][1]); + std::string output_fixel_directory(opt[0][1]); if (template_fixel_directory != output_fixel_directory) { - Fixel::copy_index_file (template_fixel_directory, output_fixel_directory); - Fixel::copy_directions_file (template_fixel_directory, output_fixel_directory); + Fixel::copy_index_file(template_fixel_directory, output_fixel_directory); + Fixel::copy_directions_file(template_fixel_directory, output_fixel_directory); } - fc_output_data = Image::create (Path::join (output_fixel_directory, opt[0][2]), Fixel::data_header_from_index (fixel_template_index)); + fc_output_data = Image::create(Path::join(output_fixel_directory, opt[0][2]), + Fixel::data_header_from_index(fixel_template_index)); } - - opt = get_options ("jmat"); + opt = get_options("jmat"); if (opt.size()) { - Header output_header (input); + Header output_header(input); output_header.size(3) = 9; - jmatrix_output = Image::create (opt[0][0], output_header); + jmatrix_output = Image::create(opt[0][0], output_header); } - opt = get_options ("jdet"); + opt = get_options("jdet"); if (opt.size()) { - Header output_header (input); + Header output_header(input); output_header.ndim() = 3; - jdeterminant_output = Image::create (opt[0][0], output_header); + jdeterminant_output = Image::create(opt[0][0], output_header); } if (!(jmatrix_output.valid() || jdeterminant_output.valid() || fc_output_data.valid())) - throw Exception ("Nothing to do; please specify at least one output image type"); + throw Exception("Nothing to do; please specify at least one output image type"); - Adapter::Jacobian > jacobian (input); + Adapter::Jacobian> jacobian(input); - for (auto i = Loop ("outputting warp metric(s)", jacobian, 0, 3) (jacobian); i; ++i) { + for (auto i = Loop("outputting warp metric(s)", jacobian, 0, 3)(jacobian); i; ++i) { auto jacobian_matrix = jacobian.value(); if (fc_output_data.valid()) { - assign_pos_of (jacobian, 0, 3).to (fixel_template_index); - for (auto f = Fixel::Loop (fixel_template_index) (fixel_template_directions, fc_output_data); f; ++f) { + assign_pos_of(jacobian, 0, 3).to(fixel_template_index); + for (auto f = Fixel::Loop(fixel_template_index)(fixel_template_directions, fc_output_data); f; ++f) { Eigen::Vector3f fixel_direction = fixel_template_directions.row(1); fixel_direction.normalize(); Eigen::Vector3f fixel_direction_transformed = jacobian_matrix * fixel_direction; @@ -125,14 +122,14 @@ void run () } } if (jmatrix_output.valid()) { - assign_pos_of (jacobian, 0, 3).to (jmatrix_output); + assign_pos_of(jacobian, 0, 3).to(jmatrix_output); for (size_t j = 0; j < 9; ++j) { jmatrix_output.index(3) = j; jmatrix_output.value() = jacobian_matrix.data()[j]; } } if (jdeterminant_output.valid()) { - assign_pos_of (jacobian, 0, 3).to (jdeterminant_output); + assign_pos_of(jacobian, 0, 3).to(jdeterminant_output); jdeterminant_output.value() = jacobian_matrix.determinant(); } } diff --git a/cmd/warpconvert.cpp b/cmd/warpconvert.cpp index 404d10abd3..e1d5e81d14 100644 --- a/cmd/warpconvert.cpp +++ b/cmd/warpconvert.cpp @@ -14,134 +14,138 @@ * For more details, see http://www.mrtrix.org/. */ +#include "adapter/extract.h" #include "command.h" +#include "file/nifti_utils.h" #include "image.h" -#include "registration/warp/helpers.h" #include "registration/warp/compose.h" #include "registration/warp/convert.h" -#include "adapter/extract.h" -#include "file/nifti_utils.h" - +#include "registration/warp/helpers.h" using namespace MR; using namespace App; -const char* conversion_types[] = {"deformation2displacement","displacement2deformation","warpfull2deformation","warpfull2displacement",nullptr}; - +const char *conversion_types[] = { + "deformation2displacement", "displacement2deformation", "warpfull2deformation", "warpfull2displacement", nullptr}; -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Convert between different representations of a non-linear warp"; DESCRIPTION - + "A deformation field is defined as an image where each voxel " - "defines the corresponding position in the other image (in scanner space coordinates). A displacement field " - "stores the displacements (in mm) to the other image from the each voxel's position (in scanner space). The warpfull file is the " - "5D format output from mrregister -nl_warp_full, which contains linear transforms, warps and their inverses that map each image to a midway space."; //TODO at link to warp format documentation + +"A deformation field is defined as an image where each voxel " + "defines the corresponding position in the other image (in scanner space coordinates). A displacement field " + "stores the displacements (in mm) to the other image from the each voxel's position (in scanner space). The " + "warpfull file is the " + "5D format output from mrregister -nl_warp_full, which contains linear transforms, warps and their inverses that " + "map each image to a midway space."; // TODO at link to warp format documentation ARGUMENTS - + Argument ("in", "the input warp image.").type_image_in () - + Argument ("type", "the conversion type required. Valid choices are: " + join(conversion_types, ", ")).type_choice (conversion_types) - + Argument ("out", "the output warp image.").type_image_out (); + +Argument("in", "the input warp image.").type_image_in() + + Argument("type", "the conversion type required. Valid choices are: " + join(conversion_types, ", ")) + .type_choice(conversion_types) + + Argument("out", "the output warp image.").type_image_out(); OPTIONS - + Option ("template", "define a template image when converting a warpfull file (which is defined on a grid in the midway space between image 1 & 2). For example to " - "generate the deformation field that maps image1 to image2, then supply image2 as the template image") - + Argument ("image").type_image_in () - - + Option ("midway_space", - "to be used only with warpfull2deformation and warpfull2displacement conversion types. The output will only contain the non-linear warp to map an input " - "image to the midway space (defined by the warpfull grid). If a linear transform exists in the warpfull file header then it will be composed and included in the output.") - - + Option ("from", - "to be used only with warpfull2deformation and warpfull2displacement conversion types. Used to define the direction of the desired output field." - "Use -from 1 to obtain the image1->image2 field and from 2 for image2->image1. Can be used in combination with the -midway_space option to " - "produce a field that only maps to midway space.") - + Argument ("image").type_integer (1, 2); + +Option("template", + "define a template image when converting a warpfull file (which is defined on a grid in the midway space " + "between image 1 & 2). For example to " + "generate the deformation field that maps image1 to image2, then supply image2 as the template image") + + Argument("image").type_image_in() + + + Option("midway_space", + "to be used only with warpfull2deformation and warpfull2displacement conversion types. The output will " + "only contain the non-linear warp to map an input " + "image to the midway space (defined by the warpfull grid). If a linear transform exists in the warpfull " + "file header then it will be composed and included in the output.") + + + Option("from", + "to be used only with warpfull2deformation and warpfull2displacement conversion types. Used to define " + "the direction of the desired output field." + "Use -from 1 to obtain the image1->image2 field and from 2 for image2->image1. Can be used in " + "combination with the -midway_space option to " + "produce a field that only maps to midway space.") + + Argument("image").type_integer(1, 2); } - -void run () -{ +void run() { const int type = argument[1]; bool midway_space = get_options("midway_space").size() ? true : false; std::string template_filename; - auto opt = get_options ("template"); + auto opt = get_options("template"); if (opt.size()) template_filename = str(opt[0][0]); int from = 1; - opt = get_options ("from"); + opt = get_options("from"); if (opt.size()) from = opt[0][0]; // deformation2displacement if (type == 0) { if (midway_space) - WARN ("-midway_space option ignored with deformation2displacement conversion type"); - if (get_options ("template").size()) - WARN ("-template option ignored with deformation2displacement conversion type"); - if (get_options ("from").size()) - WARN ("-from option ignored with deformation2displacement conversion type"); + WARN("-midway_space option ignored with deformation2displacement conversion type"); + if (get_options("template").size()) + WARN("-template option ignored with deformation2displacement conversion type"); + if (get_options("from").size()) + WARN("-from option ignored with deformation2displacement conversion type"); - auto deformation = Image::open (argument[0]).with_direct_io (3); - Registration::Warp::check_warp (deformation); + auto deformation = Image::open(argument[0]).with_direct_io(3); + Registration::Warp::check_warp(deformation); - Header header (deformation); - header.datatype() = DataType::from_command_line (DataType::Float32); - Image displacement = Image::create (argument[2], header).with_direct_io(); - Registration::Warp::deformation2displacement (deformation, displacement); + Header header(deformation); + header.datatype() = DataType::from_command_line(DataType::Float32); + Image displacement = Image::create(argument[2], header).with_direct_io(); + Registration::Warp::deformation2displacement(deformation, displacement); - // displacement2deformation + // displacement2deformation } else if (type == 1) { - auto displacement = Image::open (argument[0]).with_direct_io (3); - Registration::Warp::check_warp (displacement); + auto displacement = Image::open(argument[0]).with_direct_io(3); + Registration::Warp::check_warp(displacement); if (midway_space) - WARN ("-midway_space option ignored with displacement2deformation conversion type"); - if (get_options ("template").size()) - WARN ("-template option ignored with displacement2deformation conversion type"); - if (get_options ("from").size()) - WARN ("-from option ignored with displacement2deformation conversion type"); - - Header header (displacement); - header.datatype() = DataType::from_command_line (DataType::Float32); - Image deformation = Image::create (argument[2], header).with_direct_io(); - Registration::Warp::displacement2deformation (displacement, deformation); - - // warpfull2deformation & warpfull2displacement + WARN("-midway_space option ignored with displacement2deformation conversion type"); + if (get_options("template").size()) + WARN("-template option ignored with displacement2deformation conversion type"); + if (get_options("from").size()) + WARN("-from option ignored with displacement2deformation conversion type"); + + Header header(displacement); + header.datatype() = DataType::from_command_line(DataType::Float32); + Image deformation = Image::create(argument[2], header).with_direct_io(); + Registration::Warp::displacement2deformation(displacement, deformation); + + // warpfull2deformation & warpfull2displacement } else if (type == 2 || type == 3) { - if (!Path::is_mrtrix_image (argument[0]) && !(Path::has_suffix (argument[0], {".nii", ".nii.gz"}) && - File::Config::get_bool ("NIfTIAutoLoadJSON", false) && - Path::exists(File::NIfTI::get_json_path(opt[0][0])))) - WARN ("warp_full image is not in original .mif/.mih file format or in NIfTI file format with associated JSON. " - "Converting to other file formats may remove linear transformations stored in the image header."); - auto warp = Image::open (argument[0]).with_direct_io (3); - Registration::Warp::check_warp_full (warp); + if (!Path::is_mrtrix_image(argument[0]) && + !(Path::has_suffix(argument[0], {".nii", ".nii.gz"}) && File::Config::get_bool("NIfTIAutoLoadJSON", false) && + Path::exists(File::NIfTI::get_json_path(opt[0][0])))) + WARN("warp_full image is not in original .mif/.mih file format or in NIfTI file format with associated JSON. " + "Converting to other file formats may remove linear transformations stored in the image header."); + auto warp = Image::open(argument[0]).with_direct_io(3); + Registration::Warp::check_warp_full(warp); Image warp_output; if (midway_space) { - warp_output = Registration::Warp::compute_midway_deformation (warp, from); + warp_output = Registration::Warp::compute_midway_deformation(warp, from); } else { - if (!get_options ("template").size()) - throw Exception ("-template option required with warpfull2deformation or warpfull2displacement conversion type"); - auto template_header = Header::open (template_filename); - warp_output = Registration::Warp::compute_full_deformation (warp, template_header, from); + if (!get_options("template").size()) + throw Exception("-template option required with warpfull2deformation or warpfull2displacement conversion type"); + auto template_header = Header::open(template_filename); + warp_output = Registration::Warp::compute_full_deformation(warp, template_header, from); } if (type == 3) - Registration::Warp::deformation2displacement (warp_output, warp_output); + Registration::Warp::deformation2displacement(warp_output, warp_output); - Header header (warp_output); - header.datatype() = DataType::from_command_line (DataType::Float32); - Image output = Image::create (argument[2], header); - threaded_copy_with_progress_message ("converting warp", warp_output, output); + Header header(warp_output); + header.datatype() = DataType::from_command_line(DataType::Float32); + Image output = Image::create(argument[2], header); + threaded_copy_with_progress_message("converting warp", warp_output, output); } else { - throw Exception ("Unsupported warp conversion type"); + throw Exception("Unsupported warp conversion type"); } - } diff --git a/cmd/warpcorrect.cpp b/cmd/warpcorrect.cpp index ff2bc64c15..6abba6e11e 100644 --- a/cmd/warpcorrect.cpp +++ b/cmd/warpcorrect.cpp @@ -13,114 +13,97 @@ * * For more details, see http://www.mrtrix.org/. */ +#include "algo/threaded_loop.h" #include "command.h" #include "image.h" -#include "algo/threaded_loop.h" #include "registration/warp/helpers.h" - using namespace MR; using namespace App; - const float PRECISION = Eigen::NumTraits::dummy_precision(); -void usage () -{ +void usage() { AUTHOR = "David Raffelt (david.raffelt@florey.edu.au) & Max Pietsch (mail@maxpietsch.com)"; SYNOPSIS = "Replaces voxels in a deformation field that point to a specific out of bounds location with nan,nan,nan"; DESCRIPTION - + "This can be used in conjunction with the warpinit command to compute a MRtrix " - "compatible deformation field from non-linear transformations generated by any other registration package."; + +"This can be used in conjunction with the warpinit command to compute a MRtrix " + "compatible deformation field from non-linear transformations generated by any other registration package."; ARGUMENTS - + Argument ("in", "the input warp image.").type_image_in () - + Argument ("out", "the output warp image.").type_image_out (); + +Argument("in", "the input warp image.").type_image_in() + Argument("out", "the output warp image.").type_image_out(); OPTIONS - + Option ("marker", "single value or a comma separated list of values that define out of bounds voxels in the input warp image." - " Default: (0,0,0).") - + Argument ("coordinates").type_sequence_float() - + Option ("tolerance", "numerical precision used for L2 matrix norm comparison. Default: " + str(PRECISION) + ".") - + Argument ("value").type_float(PRECISION); + +Option("marker", + "single value or a comma separated list of values that define out of bounds voxels in the input warp image." + " Default: (0,0,0).") + + Argument("coordinates").type_sequence_float() + + Option("tolerance", "numerical precision used for L2 matrix norm comparison. Default: " + str(PRECISION) + ".") + + Argument("value").type_float(PRECISION); } - using value_type = float; class BoundsCheck { - public: - BoundsCheck (value_type tolerance, const Eigen::Matrix& marker, size_t& total_count): - precision (tolerance), - vec (marker), - counter (total_count), - count (0), - val ({NaN, NaN, NaN}) { } - BoundsCheck (const BoundsCheck& that) : - precision (that.precision), - vec (that.vec), - counter (that.counter), - count (0), - val ({NaN, NaN, NaN}) { } - template - void operator() (ImageTypeIn& in, ImageTypeOut& out) - { - val = Eigen::Matrix(in.row(3)); - if ((vec - val).isMuchSmallerThan(precision) || (vec.hasNaN() && val.hasNaN())) { - count++; - for (auto l = Loop (3) (out); l; ++l) - out.value() = NaN; - } else { - for (auto l = Loop (3) (in, out); l; ++l) - out.value() = in.value(); - } - } - virtual ~BoundsCheck () { - counter += count; +public: + BoundsCheck(value_type tolerance, const Eigen::Matrix &marker, size_t &total_count) + : precision(tolerance), vec(marker), counter(total_count), count(0), val({NaN, NaN, NaN}) {} + BoundsCheck(const BoundsCheck &that) + : precision(that.precision), vec(that.vec), counter(that.counter), count(0), val({NaN, NaN, NaN}) {} + template void operator()(ImageTypeIn &in, ImageTypeOut &out) { + val = Eigen::Matrix(in.row(3)); + if ((vec - val).isMuchSmallerThan(precision) || (vec.hasNaN() && val.hasNaN())) { + count++; + for (auto l = Loop(3)(out); l; ++l) + out.value() = NaN; + } else { + for (auto l = Loop(3)(in, out); l; ++l) + out.value() = in.value(); } - protected: - const value_type precision; - const Eigen::Matrix vec; - size_t& counter; - size_t count; - Eigen::Matrix val; + } + virtual ~BoundsCheck() { counter += count; } + +protected: + const value_type precision; + const Eigen::Matrix vec; + size_t &counter; + size_t count; + Eigen::Matrix val; }; +void run() { + auto input = Image::open(argument[0]).with_direct_io(3); + Registration::Warp::check_warp(input); -void run () -{ - auto input = Image::open (argument[0]).with_direct_io (3); - Registration::Warp::check_warp (input); - - auto output = Image::create (argument[1], input); + auto output = Image::create(argument[1], input); - Eigen::Matrix oob_vector = Eigen::Matrix::Zero(); - auto opt = get_options ("marker"); + Eigen::Matrix oob_vector = Eigen::Matrix::Zero(); + auto opt = get_options("marker"); if (opt.size() == 1) { - const auto loc = parse_floats (opt[0][0]); + const auto loc = parse_floats(opt[0][0]); if (loc.size() == 1) { oob_vector.fill(loc[0]); } else if (loc.size() == 3) { - for (auto i=0; i<3; i++) + for (auto i = 0; i < 3; i++) oob_vector[i] = loc[i]; - } else throw Exception("location option requires either single value or list of 3 values"); + } else + throw Exception("location option requires either single value or list of 3 values"); } - opt = get_options ("tolerance"); + opt = get_options("tolerance"); value_type precision = PRECISION; if (opt.size()) precision = opt[0][0]; - size_t count (0); - auto func = BoundsCheck (precision, oob_vector, count); + size_t count(0); + auto func = BoundsCheck(precision, oob_vector, count); - ThreadedLoop ("correcting warp", input, 0, 3) - .run (func, input, output); + ThreadedLoop("correcting warp", input, 0, 3).run(func, input, output); if (count == 0) - WARN("no out of bounds voxels found with value (" + - str(oob_vector[0]) + "," + str(oob_vector[1]) + "," + str(oob_vector[2]) + ")"); + WARN("no out of bounds voxels found with value (" + str(oob_vector[0]) + "," + str(oob_vector[1]) + "," + + str(oob_vector[2]) + ")"); INFO("converted " + str(count) + " out of bounds values"); } diff --git a/cmd/warpinit.cpp b/cmd/warpinit.cpp index 1de90fd400..25148a8476 100644 --- a/cmd/warpinit.cpp +++ b/cmd/warpinit.cpp @@ -14,63 +14,58 @@ * For more details, see http://www.mrtrix.org/. */ +#include "algo/threaded_loop.h" #include "command.h" #include "image.h" #include "stride.h" #include "transform.h" -#include "algo/threaded_loop.h" - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "J-Donald Tournier (jdtournier@gmail.com)"; SYNOPSIS = "Create an initial warp image, representing an identity transformation"; DESCRIPTION - + "This is useful to obtain the warp fields from other normalisation " - "applications, by applying the transformation of interest to the " - "warp field generated by this program." + +"This is useful to obtain the warp fields from other normalisation " + "applications, by applying the transformation of interest to the " + "warp field generated by this program." - + "The image generated is a 4D image with the same spatial characteristics as " - "the input template image. It contains 3 volumes, with each voxel containing " - "its own x,y,z coordinates." + + "The image generated is a 4D image with the same spatial characteristics as " + "the input template image. It contains 3 volumes, with each voxel containing " + "its own x,y,z coordinates." - + "Note that this command can be used to create 3 separate X,Y,Z images " - "directly (which may be useful to create images suitable for use in the " - "registration program) using the following syntax:" + + "Note that this command can be used to create 3 separate X,Y,Z images " + "directly (which may be useful to create images suitable for use in the " + "registration program) using the following syntax:" - + " $ warpinit template.mif warp-'[]'.nii"; + + " $ warpinit template.mif warp-'[]'.nii"; ARGUMENTS - + Argument ("template", "the input template image.").type_image_in () - + Argument ("warp", "the output warp image.").type_image_out (); + +Argument("template", "the input template image.").type_image_in() + + Argument("warp", "the output warp image.").type_image_out(); } - -void run () -{ - auto header = Header::open (argument[0]); +void run() { + auto header = Header::open(argument[0]); header.datatype() = DataType::Float32; header.ndim() = 4; header.size(3) = 3; - Stride::set (header, Stride::contiguous_along_axis (3, header)); + Stride::set(header, Stride::contiguous_along_axis(3, header)); auto warp = Image::create(argument[1], header); - Transform transform (header); + Transform transform(header); - auto func = [&transform](Image& image) { - Eigen::Vector3d voxel_pos ((float)image.index(0), (float)image.index(1), (float)image.index(2)); + auto func = [&transform](Image &image) { + Eigen::Vector3d voxel_pos((float)image.index(0), (float)image.index(1), (float)image.index(2)); Eigen::Vector3f scanner_pos = (transform.voxel2scanner * voxel_pos).cast(); - for (auto l = Loop (3) (image); l; ++l) + for (auto l = Loop(3)(image); l; ++l) image.value() = scanner_pos[image.index(3)]; }; - ThreadedLoop ("generating identity warp", warp, 0, 3) - .run (func, warp); + ThreadedLoop("generating identity warp", warp, 0, 3).run(func, warp); } diff --git a/cmd/warpinvert.cpp b/cmd/warpinvert.cpp index 898c478817..e0d37fec9e 100644 --- a/cmd/warpinvert.cpp +++ b/cmd/warpinvert.cpp @@ -21,45 +21,42 @@ #include "registration/warp/helpers.h" #include "registration/warp/invert.h" - using namespace MR; using namespace App; -void usage () -{ +void usage() { AUTHOR = "Robert E. Smith (robert.smith@florey.edu.au) and David Raffelt (david.raffelt@florey.edu.au)"; SYNOPSIS = "Invert a non-linear warp field"; DESCRIPTION - + "By default, this command assumes that the input warp field is a deformation field, i.e. each voxel " - "stores the corresponding position in the other image (in scanner space), and the calculated output " - "warp image will also be a deformation field. If the input warp field is instead a displacment field, " - "i.e. where each voxel stores an offset from which to sample the other image (but still in scanner " - "space), then the -displacement option should be used; the output warp field will additionally be " - "calculated as a displacement field in this case."; + +"By default, this command assumes that the input warp field is a deformation field, i.e. each voxel " + "stores the corresponding position in the other image (in scanner space), and the calculated output " + "warp image will also be a deformation field. If the input warp field is instead a displacment field, " + "i.e. where each voxel stores an offset from which to sample the other image (but still in scanner " + "space), then the -displacement option should be used; the output warp field will additionally be " + "calculated as a displacement field in this case."; ARGUMENTS - + Argument ("in", "the input warp image.").type_image_in () - + Argument ("out", "the output warp image.").type_image_out (); + +Argument("in", "the input warp image.").type_image_in() + Argument("out", "the output warp image.").type_image_out(); OPTIONS - + Option ("template", "define a template image grid for the output warp") - + Argument ("image").type_image_in () + +Option("template", "define a template image grid for the output warp") + Argument("image").type_image_in() - + Option ("displacement", "indicates that the input warp field is a displacement field; the output will also be a displacement field"); + + + Option( + "displacement", + "indicates that the input warp field is a displacement field; the output will also be a displacement field"); } - -void run () -{ - const bool displacement = get_options ("displacement").size(); - Header header_in (Header::open (argument[0])); - Registration::Warp::check_warp (header_in); - Header header_out (header_in); - auto opt = get_options ("template"); +void run() { + const bool displacement = get_options("displacement").size(); + Header header_in(Header::open(argument[0])); + Registration::Warp::check_warp(header_in); + Header header_out(header_in); + auto opt = get_options("template"); if (opt.size()) { - header_out = Header::open (opt[0][0]); + header_out = Header::open(opt[0][0]); if (displacement) { header_out.ndim() = 3; } else { @@ -70,12 +67,12 @@ void run () header_out.datatype().set_byte_order_native(); } - Image image_in (header_in.get_image()); - Image image_out (Image::create (argument[1], header_out)); + Image image_in(header_in.get_image()); + Image image_out(Image::create(argument[1], header_out)); if (displacement) { - Registration::Warp::invert_displacement (image_in, image_out); + Registration::Warp::invert_displacement(image_in, image_out); } else { - Registration::Warp::invert_deformation (image_in, image_out); + Registration::Warp::invert_deformation(image_in, image_out); } } diff --git a/core/adapter/base.h b/core/adapter/base.h index e8bc420074..931ea0bf06 100644 --- a/core/adapter/base.h +++ b/core/adapter/base.h @@ -20,71 +20,59 @@ #include "image_helpers.h" #include "types.h" -namespace MR -{ - class Header; +namespace MR { +class Header; - namespace Adapter - { +namespace Adapter { - template